Compare commits
	
		
			6 commits
		
	
	
		
			
				main
			
			...
			
				rework-ids
			
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							 | 
						
							
							
								
							
							
	
	
	b9624f5f65 | 
						
						
							||
| 
							 | 
						
							
							
								
							
							
	
	
	8441fccea2 | 
						
						
							||
| 
							 | 
						
							
							
								
							
							
	
	
	1f50c386b8 | 
						
						
							||
| 
							 | 
						
							
							
								
							
							
	
	
	65751aeb45 | 
						
						
							||
| 
							 | 
						
							
							
								
							
							
	
	
	29820f9d50 | 
						
						
							||
| 
							 | 
						
							
							
								
							
							
	
	
	74a8af6ca6 | 
						
						
							
					 44 changed files with 863 additions and 661 deletions
				
			
		
							
								
								
									
										9
									
								
								.github/workflows/ci.yaml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ci.yaml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -53,6 +53,15 @@ jobs:
 | 
			
		|||
        with:
 | 
			
		||||
          command: check ${{ matrix.checks }}
 | 
			
		||||
 | 
			
		||||
  js_tests:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Install wasm-pack
 | 
			
		||||
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
 | 
			
		||||
      - name: run tests
 | 
			
		||||
        run: ./scripts/ci/js_tests
 | 
			
		||||
 | 
			
		||||
  linux:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
    "mocha": "^9.1.1"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "automerge-wasm": "file:../automerge-wasm",
 | 
			
		||||
    "automerge-wasm": "file:../dev",
 | 
			
		||||
    "fast-sha256": "^1.3.0",
 | 
			
		||||
    "pako": "^2.0.4",
 | 
			
		||||
    "uuid": "^8.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ use std::collections::{HashMap, HashSet};
 | 
			
		|||
use std::convert::TryFrom;
 | 
			
		||||
use std::convert::TryInto;
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
use wasm_bindgen::prelude::*;
 | 
			
		||||
use wasm_bindgen::JsCast;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +150,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result<Array, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let result = if let Some(heads) = get_heads(heads) {
 | 
			
		||||
            self.0.keys_at(obj, &heads)
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +163,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        if let Some(heads) = get_heads(heads) {
 | 
			
		||||
            self.0.text_at(obj, &heads)
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +180,7 @@ impl Automerge {
 | 
			
		|||
        delete_count: JsValue,
 | 
			
		||||
        text: JsValue,
 | 
			
		||||
    ) -> Result<(), JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let start = to_usize(start, "start")?;
 | 
			
		||||
        let delete_count = to_usize(delete_count, "deleteCount")?;
 | 
			
		||||
        let mut vals = vec![];
 | 
			
		||||
| 
						 | 
				
			
			@ -214,7 +215,7 @@ impl Automerge {
 | 
			
		|||
        value: JsValue,
 | 
			
		||||
        datatype: JsValue,
 | 
			
		||||
    ) -> Result<JsValue, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        //let key = self.insert_pos_for_index(&obj, prop)?;
 | 
			
		||||
        let index: Result<_, JsValue> = index
 | 
			
		||||
            .as_f64()
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +236,7 @@ impl Automerge {
 | 
			
		|||
        value: JsValue,
 | 
			
		||||
        datatype: JsValue,
 | 
			
		||||
    ) -> Result<JsValue, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let prop = self.import_prop(prop)?;
 | 
			
		||||
        let value = self.import_value(value, datatype)?;
 | 
			
		||||
        let opid = self.0.set(obj, prop, value).map_err(to_js_err)?;
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +247,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn inc(&mut self, obj: JsValue, prop: JsValue, value: JsValue) -> Result<(), JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let prop = self.import_prop(prop)?;
 | 
			
		||||
        let value: f64 = value
 | 
			
		||||
            .as_f64()
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +258,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn value(&mut self, obj: JsValue, prop: JsValue, heads: JsValue) -> Result<Array, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let result = Array::new();
 | 
			
		||||
        let prop = to_prop(prop);
 | 
			
		||||
        let heads = get_heads(heads);
 | 
			
		||||
| 
						 | 
				
			
			@ -284,7 +285,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn values(&mut self, obj: JsValue, arg: JsValue, heads: JsValue) -> Result<Array, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let result = Array::new();
 | 
			
		||||
        let prop = to_prop(arg);
 | 
			
		||||
        if let Ok(prop) = prop {
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +317,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        if let Some(heads) = get_heads(heads) {
 | 
			
		||||
            Ok((self.0.length_at(obj, &heads) as f64).into())
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -325,7 +326,7 @@ impl Automerge {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn del(&mut self, obj: JsValue, prop: JsValue) -> Result<(), JsValue> {
 | 
			
		||||
        let obj = self.import(obj)?;
 | 
			
		||||
        let obj: automerge::ObjId = self.import(obj)?;
 | 
			
		||||
        let prop = to_prop(prop)?;
 | 
			
		||||
        self.0.del(obj, prop).map_err(to_js_err)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
| 
						 | 
				
			
			@ -442,16 +443,18 @@ impl Automerge {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn export<E: automerge::Exportable>(&self, val: E) -> JsValue {
 | 
			
		||||
        self.0.export(val).into()
 | 
			
		||||
    fn export<D: std::fmt::Display>(&self, val: D) -> JsValue {
 | 
			
		||||
        val.to_string().into()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn import<I: automerge::Importable>(&self, id: JsValue) -> Result<I, JsValue> {
 | 
			
		||||
        let id_str = id
 | 
			
		||||
    fn import<F: FromStr>(&self, id: JsValue) -> Result<F, JsValue> 
 | 
			
		||||
        where F::Err: std::fmt::Display
 | 
			
		||||
    {
 | 
			
		||||
        id
 | 
			
		||||
            .as_string()
 | 
			
		||||
            .ok_or("invalid opid/objid/elemid")
 | 
			
		||||
            .map_err(to_js_err)?;
 | 
			
		||||
        self.0.import(&id_str).map_err(to_js_err)
 | 
			
		||||
            .ok_or("invalid opid/objid/elemid")?
 | 
			
		||||
            .parse::<F>()
 | 
			
		||||
            .map_err(to_js_err)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn import_prop(&mut self, prop: JsValue) -> Result<Prop, JsValue> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,8 @@ use crate::decoding::{Decodable, InvalidChangeError};
 | 
			
		|||
use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
 | 
			
		||||
use crate::legacy as amp;
 | 
			
		||||
use crate::{
 | 
			
		||||
    ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD,
 | 
			
		||||
    ROOT,
 | 
			
		||||
    types::{ObjId, OpId},
 | 
			
		||||
    ActorId, AutomergeError, ElemId, IndexedCache, Key, Op, OpType, Transaction, HEAD,
 | 
			
		||||
};
 | 
			
		||||
use core::ops::Range;
 | 
			
		||||
use flate2::{
 | 
			
		||||
| 
						 | 
				
			
			@ -417,10 +417,9 @@ fn increment_range_map(ranges: &mut HashMap<u32, Range<usize>>, len: usize) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId {
 | 
			
		||||
    if id.0 == ROOT {
 | 
			
		||||
        amp::ObjectId::Root
 | 
			
		||||
    } else {
 | 
			
		||||
        export_opid(&id.0, actors).into()
 | 
			
		||||
    match id {
 | 
			
		||||
        ObjId::Root => amp::ObjectId::Root,
 | 
			
		||||
        ObjId::Op(op) => export_opid(op, actors).into() 
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -433,7 +432,7 @@ fn export_elemid(id: &ElemId, actors: &IndexedCache<ActorId>) -> amp::ElementId
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
fn export_opid(id: &OpId, actors: &IndexedCache<ActorId>) -> amp::OpId {
 | 
			
		||||
    amp::OpId(id.0, actors.get(id.1).clone())
 | 
			
		||||
    amp::OpId(id.counter(), actors.get(id.actor()).clone())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn export_op(op: &Op, actors: &IndexedCache<ActorId>, props: &IndexedCache<String>) -> amp::Op {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use crate::OpId;
 | 
			
		||||
use crate::types::OpId;
 | 
			
		||||
use fxhash::FxBuildHasher;
 | 
			
		||||
use std::cmp;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,8 +19,8 @@ impl Clock {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn covers(&self, id: &OpId) -> bool {
 | 
			
		||||
        if let Some(val) = self.0.get(&id.1) {
 | 
			
		||||
            val >= &id.0
 | 
			
		||||
        if let Some(val) = self.0.get(&id.actor()) {
 | 
			
		||||
            val >= &id.counter()
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -38,15 +38,15 @@ mod tests {
 | 
			
		|||
        clock.include(1, 20);
 | 
			
		||||
        clock.include(2, 10);
 | 
			
		||||
 | 
			
		||||
        assert!(clock.covers(&OpId(10, 1)));
 | 
			
		||||
        assert!(clock.covers(&OpId(20, 1)));
 | 
			
		||||
        assert!(!clock.covers(&OpId(30, 1)));
 | 
			
		||||
        assert!(clock.covers(&OpId::new(10, 1)));
 | 
			
		||||
        assert!(clock.covers(&OpId::new(20, 1)));
 | 
			
		||||
        assert!(!clock.covers(&OpId::new(30, 1)));
 | 
			
		||||
 | 
			
		||||
        assert!(clock.covers(&OpId(5, 2)));
 | 
			
		||||
        assert!(clock.covers(&OpId(10, 2)));
 | 
			
		||||
        assert!(!clock.covers(&OpId(15, 2)));
 | 
			
		||||
        assert!(clock.covers(&OpId::new(5, 2)));
 | 
			
		||||
        assert!(clock.covers(&OpId::new(10, 2)));
 | 
			
		||||
        assert!(!clock.covers(&OpId::new(15, 2)));
 | 
			
		||||
 | 
			
		||||
        assert!(!clock.covers(&OpId(1, 3)));
 | 
			
		||||
        assert!(!clock.covers(&OpId(100, 3)));
 | 
			
		||||
        assert!(!clock.covers(&OpId::new(1, 3)));
 | 
			
		||||
        assert!(!clock.covers(&OpId::new(100, 3)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,8 +11,7 @@ use std::{
 | 
			
		|||
    str,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::ROOT;
 | 
			
		||||
use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue};
 | 
			
		||||
use crate::{ActorId, ElemId, Key, ObjType, OpType, ScalarValue, types::{ObjId, OpId}};
 | 
			
		||||
 | 
			
		||||
use crate::legacy as amp;
 | 
			
		||||
use amp::SortedVec;
 | 
			
		||||
| 
						 | 
				
			
			@ -686,15 +685,15 @@ impl KeyEncoder {
 | 
			
		|||
                self.ctr.append_null();
 | 
			
		||||
                self.str.append_value(props[i].clone());
 | 
			
		||||
            }
 | 
			
		||||
            Key::Seq(ElemId(OpId(0, 0))) => {
 | 
			
		||||
            Key::Seq(ElemId(opid)) if opid.actor() == 0 && opid.counter() == 0 => {
 | 
			
		||||
                // HEAD
 | 
			
		||||
                self.actor.append_null();
 | 
			
		||||
                self.ctr.append_value(0);
 | 
			
		||||
                self.str.append_null();
 | 
			
		||||
            }
 | 
			
		||||
            Key::Seq(ElemId(OpId(ctr, actor))) => {
 | 
			
		||||
                self.actor.append_value(actors[actor]);
 | 
			
		||||
                self.ctr.append_value(ctr);
 | 
			
		||||
            Key::Seq(ElemId(opid)) => {
 | 
			
		||||
                self.actor.append_value(actors[opid.actor()]);
 | 
			
		||||
                self.ctr.append_value(opid.counter());
 | 
			
		||||
                self.str.append_null();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -773,8 +772,8 @@ impl SuccEncoder {
 | 
			
		|||
    fn append(&mut self, succ: &[OpId], actors: &[usize]) {
 | 
			
		||||
        self.num.append_value(succ.len());
 | 
			
		||||
        for s in succ.iter() {
 | 
			
		||||
            self.ctr.append_value(s.0);
 | 
			
		||||
            self.actor.append_value(actors[s.1]);
 | 
			
		||||
            self.ctr.append_value(s.counter());
 | 
			
		||||
            self.actor.append_value(actors[s.actor()]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -845,14 +844,14 @@ impl ObjEncoder {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fn append(&mut self, obj: &ObjId, actors: &[usize]) {
 | 
			
		||||
        match obj.0 {
 | 
			
		||||
            ROOT => {
 | 
			
		||||
        match obj {
 | 
			
		||||
            ObjId::Root => {
 | 
			
		||||
                self.actor.append_null();
 | 
			
		||||
                self.ctr.append_null();
 | 
			
		||||
            }
 | 
			
		||||
            OpId(ctr, actor) => {
 | 
			
		||||
                self.actor.append_value(actors[actor]);
 | 
			
		||||
                self.ctr.append_value(ctr);
 | 
			
		||||
            ObjId::Op(opid) => {
 | 
			
		||||
                self.actor.append_value(actors[opid.actor()]);
 | 
			
		||||
                self.ctr.append_value(opid.counter());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										109
									
								
								automerge/src/external_types.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								automerge/src/external_types.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,109 @@
 | 
			
		|||
use std::{borrow::Cow, fmt::Display, str::FromStr};
 | 
			
		||||
 | 
			
		||||
use crate::{op_tree::OpSetMetadata, types::OpId, ActorId};
 | 
			
		||||
 | 
			
		||||
const ROOT_STR: &str = "_root";
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
 | 
			
		||||
pub struct ExternalOpId {
 | 
			
		||||
    counter: u64,
 | 
			
		||||
    actor: ActorId,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ExternalOpId {
 | 
			
		||||
    pub(crate) fn from_internal(opid: &OpId, metadata: &OpSetMetadata) -> Option<ExternalOpId> {
 | 
			
		||||
        metadata
 | 
			
		||||
            .actors
 | 
			
		||||
            .get_safe(opid.actor())
 | 
			
		||||
            .map(|actor| ExternalOpId {
 | 
			
		||||
                counter: opid.counter(),
 | 
			
		||||
                actor: actor.clone(),
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn counter(&self) -> u64 {
 | 
			
		||||
        self.counter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn actor(&self) -> &ActorId {
 | 
			
		||||
        &self.actor
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
 | 
			
		||||
pub enum ExternalObjId<'a> {
 | 
			
		||||
    Root,
 | 
			
		||||
    Op(Cow<'a, ExternalOpId>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> ExternalObjId<'a> {
 | 
			
		||||
    pub fn into_owned(self) -> ExternalObjId<'static> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Root => ExternalObjId::Root,
 | 
			
		||||
            Self::Op(cow) => ExternalObjId::Op(Cow::<'static, _>::Owned(cow.into_owned().into())),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> From<&'a ExternalOpId> for ExternalObjId<'a> {
 | 
			
		||||
    fn from(op: &'a ExternalOpId) -> Self {
 | 
			
		||||
        ExternalObjId::Op(Cow::Borrowed(op))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ExternalOpId> for ExternalObjId<'static> {
 | 
			
		||||
    fn from(op: ExternalOpId) -> Self {
 | 
			
		||||
        ExternalObjId::Op(Cow::Owned(op))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum ParseError {
 | 
			
		||||
    #[error("op IDs should have the format <counter>@<hex encoded actor>")]
 | 
			
		||||
    BadFormat,
 | 
			
		||||
    #[error("the counter of an opid should be a positive integer")]
 | 
			
		||||
    InvalidCounter,
 | 
			
		||||
    #[error("the actor of an opid should be valid hex encoded bytes")]
 | 
			
		||||
    InvalidActor,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for ExternalOpId {
 | 
			
		||||
    type Err = ParseError;
 | 
			
		||||
 | 
			
		||||
    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
			
		||||
        let mut parts = s.split("@");
 | 
			
		||||
        let first_part = parts.next().ok_or(ParseError::BadFormat)?;
 | 
			
		||||
        let second_part = parts.next().ok_or(ParseError::BadFormat)?;
 | 
			
		||||
        let counter: u64 = first_part.parse().map_err(|_| ParseError::InvalidCounter)?;
 | 
			
		||||
        let actor: ActorId = second_part.parse().map_err(|_| ParseError::InvalidActor)?;
 | 
			
		||||
        Ok(ExternalOpId { counter, actor })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for ExternalObjId<'static> {
 | 
			
		||||
    type Err = ParseError;
 | 
			
		||||
 | 
			
		||||
    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
			
		||||
        if s == ROOT_STR {
 | 
			
		||||
            Ok(ExternalObjId::Root)
 | 
			
		||||
        } else {
 | 
			
		||||
            let op = s.parse::<ExternalOpId>()?.into();
 | 
			
		||||
            Ok(ExternalObjId::Op(Cow::Owned(op)))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for ExternalOpId {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        write!(f, "{}@{}", self.counter, self.actor)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Display for ExternalObjId<'a> {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Root => write!(f, "{}", ROOT_STR),
 | 
			
		||||
            Self::Op(op) => write!(f, "{}", op),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +43,11 @@ where
 | 
			
		|||
        &self.cache[index]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Todo replace all uses of `get` with this
 | 
			
		||||
    pub fn get_safe(&self, index: usize) -> Option<&T> {
 | 
			
		||||
        self.cache.get(index)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn sorted(&self) -> IndexedCache<T> {
 | 
			
		||||
        let mut sorted = Self::new();
 | 
			
		||||
        self.cache.iter().sorted().cloned().for_each(|item| {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,28 +40,28 @@ mod sync;
 | 
			
		|||
mod visualisation;
 | 
			
		||||
 | 
			
		||||
mod error;
 | 
			
		||||
mod external_types;
 | 
			
		||||
mod op_set;
 | 
			
		||||
mod op_tree;
 | 
			
		||||
mod query;
 | 
			
		||||
mod types;
 | 
			
		||||
mod value;
 | 
			
		||||
pub use external_types::{ExternalObjId as ObjId, ExternalOpId as OpId};
 | 
			
		||||
 | 
			
		||||
use change::{encode_document, export_change};
 | 
			
		||||
use clock::Clock;
 | 
			
		||||
use indexed_cache::IndexedCache;
 | 
			
		||||
use op_set::OpSet;
 | 
			
		||||
use std::collections::{HashMap, HashSet, VecDeque};
 | 
			
		||||
use types::{ElemId, Key, ObjId, Op, HEAD};
 | 
			
		||||
use types::{ElemId, Key, ObjId as InternalObjId, Op, HEAD};
 | 
			
		||||
use unicode_segmentation::UnicodeSegmentation;
 | 
			
		||||
 | 
			
		||||
pub use change::{decode_change, Change};
 | 
			
		||||
pub use error::AutomergeError;
 | 
			
		||||
pub use legacy::Change as ExpandedChange;
 | 
			
		||||
pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState};
 | 
			
		||||
pub use types::{
 | 
			
		||||
    ActorId, ChangeHash, Export, Exportable, Importable, ObjType, OpId, OpType, Patch, Peer, Prop,
 | 
			
		||||
    ROOT,
 | 
			
		||||
};
 | 
			
		||||
pub use types::{ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop};
 | 
			
		||||
use types::{Export, Exportable, Importable, OpId as InternalOpId};
 | 
			
		||||
pub use value::{ScalarValue, Value};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
| 
						 | 
				
			
			@ -96,25 +96,25 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    pub fn set_actor(&mut self, actor: ActorId) {
 | 
			
		||||
        self.ensure_transaction_closed();
 | 
			
		||||
        self.actor = Some(self.ops.m.actors.cache(actor))
 | 
			
		||||
        self.actor = Some(self.ops.m.borrow_mut().actors.cache(actor))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn random_actor(&mut self) -> ActorId {
 | 
			
		||||
        let actor = ActorId::from(uuid::Uuid::new_v4().as_bytes().to_vec());
 | 
			
		||||
        self.actor = Some(self.ops.m.actors.cache(actor.clone()));
 | 
			
		||||
        self.actor = Some(self.ops.m.borrow_mut().actors.cache(actor.clone()));
 | 
			
		||||
        actor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_actor(&mut self) -> ActorId {
 | 
			
		||||
        if let Some(actor) = self.actor {
 | 
			
		||||
            self.ops.m.actors[actor].clone()
 | 
			
		||||
            self.ops.m.borrow().actors[actor].clone()
 | 
			
		||||
        } else {
 | 
			
		||||
            self.random_actor()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn maybe_get_actor(&self) -> Option<ActorId> {
 | 
			
		||||
        self.actor.map(|i| self.ops.m.actors[i].clone())
 | 
			
		||||
        self.actor.map(|i| self.ops.m.borrow().actors[i].clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_actor_index(&mut self) -> usize {
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ impl Automerge {
 | 
			
		|||
            max_op: 0,
 | 
			
		||||
            transaction: None,
 | 
			
		||||
        };
 | 
			
		||||
        am.actor = Some(am.ops.m.actors.cache(actor));
 | 
			
		||||
        am.actor = Some(am.ops.m.borrow_mut().actors.cache(actor));
 | 
			
		||||
        am
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -199,7 +199,9 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    pub fn ensure_transaction_closed(&mut self) {
 | 
			
		||||
        if let Some(tx) = self.transaction.take() {
 | 
			
		||||
            self.update_history(export_change(&tx, &self.ops.m.actors, &self.ops.m.props));
 | 
			
		||||
            let change =
 | 
			
		||||
                export_change(&tx, &self.ops.m.borrow().actors, &self.ops.m.borrow().props);
 | 
			
		||||
            self.update_history(change);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -224,9 +226,9 @@ impl Automerge {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn next_id(&mut self) -> OpId {
 | 
			
		||||
    fn next_id(&mut self) -> InternalOpId {
 | 
			
		||||
        let tx = self.tx();
 | 
			
		||||
        OpId(tx.start_op + tx.operations.len() as u64, tx.actor)
 | 
			
		||||
        InternalOpId::new(tx.start_op + tx.operations.len() as u64, tx.actor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn insert_local_op(&mut self, op: Op, pos: usize, succ_pos: &[usize]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -262,22 +264,26 @@ impl Automerge {
 | 
			
		|||
    // PropAt::()
 | 
			
		||||
    // NthAt::()
 | 
			
		||||
 | 
			
		||||
    pub fn keys(&self, obj: OpId) -> Vec<String> {
 | 
			
		||||
    pub fn keys<'a, O: Into<ObjId<'a>>>(&self, obj: O) -> Vec<String> {
 | 
			
		||||
        let obj = self.import_objid(obj);
 | 
			
		||||
        let q = self.ops.search(obj.into(), query::Keys::new());
 | 
			
		||||
        q.keys.iter().map(|k| self.export(*k)).collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn keys_at(&self, obj: OpId, heads: &[ChangeHash]) -> Vec<String> {
 | 
			
		||||
    pub fn keys_at<'a, O: Into<ObjId<'a>>>(&mut self, obj: O, heads: &[ChangeHash]) -> Vec<String> {
 | 
			
		||||
        let obj = self.import_objid(obj.into());
 | 
			
		||||
        let clock = self.clock_at(heads);
 | 
			
		||||
        let q = self.ops.search(obj.into(), query::KeysAt::new(clock));
 | 
			
		||||
        q.keys.iter().map(|k| self.export(*k)).collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn length(&self, obj: OpId) -> usize {
 | 
			
		||||
    pub fn length<'a, O: Into<ObjId<'a>>>(&self, obj: O) -> usize {
 | 
			
		||||
        let obj = self.import_objid(obj.into());
 | 
			
		||||
        self.ops.search(obj.into(), query::Len::new(obj.into())).len
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn length_at(&self, obj: OpId, heads: &[ChangeHash]) -> usize {
 | 
			
		||||
    pub fn length_at<'a, O: Into<ObjId<'a>>>(&self, obj: O, heads: &[ChangeHash]) -> usize {
 | 
			
		||||
        let obj = self.import_objid(obj.into());
 | 
			
		||||
        let clock = self.clock_at(heads);
 | 
			
		||||
        self.ops.search(obj.into(), query::LenAt::new(clock)).len
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -300,23 +306,35 @@ impl Automerge {
 | 
			
		|||
    /// - The object does not exist
 | 
			
		||||
    /// - The key is the wrong type for the object
 | 
			
		||||
    /// - The key does not exist in the object
 | 
			
		||||
    pub fn set<P: Into<Prop>, V: Into<Value>>(
 | 
			
		||||
    pub fn set<'a, O: Into<ObjId<'a>>, P: Into<Prop>, V: Into<Value>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
        value: V,
 | 
			
		||||
    ) -> Result<Option<OpId>, AutomergeError> {
 | 
			
		||||
        let value = value.into();
 | 
			
		||||
        let obj = self.import_objid(obj.into());
 | 
			
		||||
        self.local_op(obj.into(), prop.into(), value.into())
 | 
			
		||||
            .map(|o| o.map(|o| self.export_opid(&o).unwrap()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn insert<V: Into<Value>>(
 | 
			
		||||
    pub fn insert<'a, O: Into<ObjId<'a>>, V: Into<Value>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        index: usize,
 | 
			
		||||
        value: V,
 | 
			
		||||
    ) -> Result<OpId, AutomergeError> {
 | 
			
		||||
        let obj = obj.into();
 | 
			
		||||
        let obj = self.import_objid(obj.into()).into();
 | 
			
		||||
        let internal_op = self.insert_internal(obj, index, value)?;
 | 
			
		||||
        Ok(self.export_opid(&internal_op).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn insert_internal<V: Into<Value>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: InternalObjId,
 | 
			
		||||
        index: usize,
 | 
			
		||||
        value: V,
 | 
			
		||||
    ) -> Result<InternalOpId, AutomergeError> {
 | 
			
		||||
        let id = self.next_id();
 | 
			
		||||
 | 
			
		||||
        let query = self.ops.search(obj, query::InsertNth::new(index));
 | 
			
		||||
| 
						 | 
				
			
			@ -341,23 +359,41 @@ impl Automerge {
 | 
			
		|||
        Ok(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn inc<P: Into<Prop>>(
 | 
			
		||||
    pub fn inc<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
        value: i64,
 | 
			
		||||
    ) -> Result<OpId, AutomergeError> {
 | 
			
		||||
        match self.local_op(obj.into(), prop.into(), OpType::Inc(value))? {
 | 
			
		||||
            Some(opid) => Ok(opid),
 | 
			
		||||
        match self.local_op(
 | 
			
		||||
            self.import_objid(obj.into()).into(),
 | 
			
		||||
            prop.into(),
 | 
			
		||||
            OpType::Inc(value),
 | 
			
		||||
        )? {
 | 
			
		||||
            Some(opid) => Ok(self.export_opid(&opid).unwrap()),
 | 
			
		||||
            None => {
 | 
			
		||||
                panic!("increment should always create a new op")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn del<P: Into<Prop>>(&mut self, obj: OpId, prop: P) -> Result<OpId, AutomergeError> {
 | 
			
		||||
    pub fn del<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
    ) -> Result<OpId, AutomergeError> {
 | 
			
		||||
        let obj = self.import_objid(obj);
 | 
			
		||||
        self.del_internal(obj, prop)
 | 
			
		||||
            .map(|o| self.export_opid(&o).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn del_internal<P: Into<Prop>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: InternalObjId,
 | 
			
		||||
        prop: P,
 | 
			
		||||
    ) -> Result<InternalOpId, AutomergeError> {
 | 
			
		||||
        // TODO: Should we also no-op multiple delete operations?
 | 
			
		||||
        match self.local_op(obj.into(), prop.into(), OpType::Del)? {
 | 
			
		||||
        match self.local_op(obj, prop.into(), OpType::Del)? {
 | 
			
		||||
            Some(opid) => Ok(opid),
 | 
			
		||||
            None => {
 | 
			
		||||
                panic!("delete should always create a new op")
 | 
			
		||||
| 
						 | 
				
			
			@ -367,27 +403,32 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert
 | 
			
		||||
    /// the new elements
 | 
			
		||||
    pub fn splice(
 | 
			
		||||
    pub fn splice<'a, O: Into<ObjId<'a>>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        mut pos: usize,
 | 
			
		||||
        del: usize,
 | 
			
		||||
        vals: Vec<Value>,
 | 
			
		||||
    ) -> Result<Vec<OpId>, AutomergeError> {
 | 
			
		||||
        let obj = self.import_objid(obj);
 | 
			
		||||
        for _ in 0..del {
 | 
			
		||||
            self.del(obj, pos)?;
 | 
			
		||||
            self.del_internal(obj, pos)?;
 | 
			
		||||
        }
 | 
			
		||||
        let mut result = Vec::with_capacity(vals.len());
 | 
			
		||||
        for v in vals {
 | 
			
		||||
            result.push(self.insert(obj, pos, v)?);
 | 
			
		||||
            result.push(self.insert_internal(obj, pos, v)?);
 | 
			
		||||
            pos += 1;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(result)
 | 
			
		||||
        let exported = result
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|o| self.export_opid(&o).unwrap())
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
        Ok(exported)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn splice_text(
 | 
			
		||||
    pub fn splice_text<'a, O: Into<ObjId<'a>>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        pos: usize,
 | 
			
		||||
        del: usize,
 | 
			
		||||
        text: &str,
 | 
			
		||||
| 
						 | 
				
			
			@ -399,8 +440,8 @@ impl Automerge {
 | 
			
		|||
        self.splice(obj, pos, del, vals)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn text(&self, obj: OpId) -> Result<String, AutomergeError> {
 | 
			
		||||
        let obj = obj.into();
 | 
			
		||||
    pub fn text<'a, O: Into<ObjId<'a>>>(&self, obj: O) -> Result<String, AutomergeError> {
 | 
			
		||||
        let obj = self.import_objid(obj.into()).into();
 | 
			
		||||
        let query = self.ops.search(obj, query::ListVals::new(obj));
 | 
			
		||||
        let mut buffer = String::new();
 | 
			
		||||
        for q in &query.ops {
 | 
			
		||||
| 
						 | 
				
			
			@ -411,9 +452,13 @@ impl Automerge {
 | 
			
		|||
        Ok(buffer)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn text_at(&self, obj: OpId, heads: &[ChangeHash]) -> Result<String, AutomergeError> {
 | 
			
		||||
    pub fn text_at<'a, O: Into<ObjId<'a>>>(
 | 
			
		||||
        &self,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        heads: &[ChangeHash],
 | 
			
		||||
    ) -> Result<String, AutomergeError> {
 | 
			
		||||
        let clock = self.clock_at(heads);
 | 
			
		||||
        let obj = obj.into();
 | 
			
		||||
        let obj = self.import_objid(obj.into()).into();
 | 
			
		||||
        let query = self.ops.search(obj, query::ListValsAt::new(clock));
 | 
			
		||||
        let mut buffer = String::new();
 | 
			
		||||
        for q in &query.ops {
 | 
			
		||||
| 
						 | 
				
			
			@ -427,38 +472,38 @@ impl Automerge {
 | 
			
		|||
    // TODO - I need to return these OpId's here **only** to get
 | 
			
		||||
    // the legacy conflicts format of { [opid]: value }
 | 
			
		||||
    // Something better?
 | 
			
		||||
    pub fn value<P: Into<Prop>>(
 | 
			
		||||
    pub fn value<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
    ) -> Result<Option<(Value, OpId)>, AutomergeError> {
 | 
			
		||||
        Ok(self.values(obj, prop.into())?.first().cloned())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn value_at<P: Into<Prop>>(
 | 
			
		||||
    pub fn value_at<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
        heads: &[ChangeHash],
 | 
			
		||||
    ) -> Result<Option<(Value, OpId)>, AutomergeError> {
 | 
			
		||||
        Ok(self.values_at(obj, prop, heads)?.first().cloned())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn values<P: Into<Prop>>(
 | 
			
		||||
    pub fn values<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
    ) -> Result<Vec<(Value, OpId)>, AutomergeError> {
 | 
			
		||||
        let obj = obj.into();
 | 
			
		||||
        let obj = self.import_objid(obj.into()).into();
 | 
			
		||||
        let result = match prop.into() {
 | 
			
		||||
            Prop::Map(p) => {
 | 
			
		||||
                let prop = self.ops.m.props.lookup(p);
 | 
			
		||||
                let prop = self.ops.m.borrow().props.lookup(p);
 | 
			
		||||
                if let Some(p) = prop {
 | 
			
		||||
                    self.ops
 | 
			
		||||
                        .search(obj, query::Prop::new(obj, p))
 | 
			
		||||
                        .ops
 | 
			
		||||
                        .into_iter()
 | 
			
		||||
                        .map(|o| o.into())
 | 
			
		||||
                        .map(|o| self.labelled_value(&o))
 | 
			
		||||
                        .collect()
 | 
			
		||||
                } else {
 | 
			
		||||
                    vec![]
 | 
			
		||||
| 
						 | 
				
			
			@ -469,30 +514,30 @@ impl Automerge {
 | 
			
		|||
                .search(obj, query::Nth::new(n))
 | 
			
		||||
                .ops
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .map(|o| o.into())
 | 
			
		||||
                .map(|o| self.labelled_value(&o))
 | 
			
		||||
                .collect(),
 | 
			
		||||
        };
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn values_at<P: Into<Prop>>(
 | 
			
		||||
    pub fn values_at<'a, O: Into<ObjId<'a>>, P: Into<Prop>>(
 | 
			
		||||
        &self,
 | 
			
		||||
        obj: OpId,
 | 
			
		||||
        obj: O,
 | 
			
		||||
        prop: P,
 | 
			
		||||
        heads: &[ChangeHash],
 | 
			
		||||
    ) -> Result<Vec<(Value, OpId)>, AutomergeError> {
 | 
			
		||||
        let prop = prop.into();
 | 
			
		||||
        let obj = obj.into();
 | 
			
		||||
        let obj = self.import_objid(obj).into();
 | 
			
		||||
        let clock = self.clock_at(heads);
 | 
			
		||||
        let result = match prop {
 | 
			
		||||
            Prop::Map(p) => {
 | 
			
		||||
                let prop = self.ops.m.props.lookup(p);
 | 
			
		||||
                let prop = self.ops.m.borrow().props.lookup(p);
 | 
			
		||||
                if let Some(p) = prop {
 | 
			
		||||
                    self.ops
 | 
			
		||||
                        .search(obj, query::PropAt::new(p, clock))
 | 
			
		||||
                        .ops
 | 
			
		||||
                        .into_iter()
 | 
			
		||||
                        .map(|o| o.into())
 | 
			
		||||
                        .map(|o| self.labelled_value(&o))
 | 
			
		||||
                        .collect()
 | 
			
		||||
                } else {
 | 
			
		||||
                    vec![]
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +548,7 @@ impl Automerge {
 | 
			
		|||
                .search(obj, query::NthAt::new(n, clock))
 | 
			
		||||
                .ops
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .map(|o| o.into())
 | 
			
		||||
                .map(|o| self.labelled_value(&o))
 | 
			
		||||
                .collect(),
 | 
			
		||||
        };
 | 
			
		||||
        Ok(result)
 | 
			
		||||
| 
						 | 
				
			
			@ -552,10 +597,10 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    fn local_op(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: ObjId,
 | 
			
		||||
        obj: InternalObjId,
 | 
			
		||||
        prop: Prop,
 | 
			
		||||
        action: OpType,
 | 
			
		||||
    ) -> Result<Option<OpId>, AutomergeError> {
 | 
			
		||||
    ) -> Result<Option<InternalOpId>, AutomergeError> {
 | 
			
		||||
        match prop {
 | 
			
		||||
            Prop::Map(s) => self.local_map_op(obj, s, action),
 | 
			
		||||
            Prop::Seq(n) => self.local_list_op(obj, n, action),
 | 
			
		||||
| 
						 | 
				
			
			@ -564,16 +609,16 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    fn local_map_op(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: ObjId,
 | 
			
		||||
        obj: InternalObjId,
 | 
			
		||||
        prop: String,
 | 
			
		||||
        action: OpType,
 | 
			
		||||
    ) -> Result<Option<OpId>, AutomergeError> {
 | 
			
		||||
    ) -> Result<Option<InternalOpId>, AutomergeError> {
 | 
			
		||||
        if prop.is_empty() {
 | 
			
		||||
            return Err(AutomergeError::EmptyStringKey);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let id = self.next_id();
 | 
			
		||||
        let prop = self.ops.m.props.cache(prop);
 | 
			
		||||
        let prop = self.ops.m.borrow_mut().props.cache(prop);
 | 
			
		||||
        let query = self.ops.search(obj, query::Prop::new(obj, prop));
 | 
			
		||||
 | 
			
		||||
        match (&query.ops[..], &action) {
 | 
			
		||||
| 
						 | 
				
			
			@ -611,10 +656,10 @@ impl Automerge {
 | 
			
		|||
 | 
			
		||||
    fn local_list_op(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        obj: ObjId,
 | 
			
		||||
        obj: InternalObjId,
 | 
			
		||||
        index: usize,
 | 
			
		||||
        action: OpType,
 | 
			
		||||
    ) -> Result<Option<OpId>, AutomergeError> {
 | 
			
		||||
    ) -> Result<Option<InternalOpId>, AutomergeError> {
 | 
			
		||||
        let query = self.ops.search(obj, query::Nth::new(index));
 | 
			
		||||
 | 
			
		||||
        let id = self.next_id();
 | 
			
		||||
| 
						 | 
				
			
			@ -675,17 +720,24 @@ impl Automerge {
 | 
			
		|||
            .iter_ops()
 | 
			
		||||
            .enumerate()
 | 
			
		||||
            .map(|(i, c)| {
 | 
			
		||||
                let actor = self.ops.m.actors.cache(change.actor_id().clone());
 | 
			
		||||
                let id = OpId(change.start_op + i as u64, actor);
 | 
			
		||||
                let actor = self
 | 
			
		||||
                    .ops
 | 
			
		||||
                    .m
 | 
			
		||||
                    .borrow_mut()
 | 
			
		||||
                    .actors
 | 
			
		||||
                    .cache(change.actor_id().clone());
 | 
			
		||||
                let id = InternalOpId::new(change.start_op + i as u64, actor);
 | 
			
		||||
                // FIXME dont need to_string()
 | 
			
		||||
                let obj: ObjId = self.import(&c.obj.to_string()).unwrap();
 | 
			
		||||
                let obj: InternalObjId = self.import(&c.obj.to_string()).unwrap();
 | 
			
		||||
                let pred = c
 | 
			
		||||
                    .pred
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|i| self.import(&i.to_string()).unwrap())
 | 
			
		||||
                    .collect();
 | 
			
		||||
                let key = match &c.key {
 | 
			
		||||
                    legacy::Key::Map(n) => Key::Map(self.ops.m.props.cache(n.to_string())),
 | 
			
		||||
                    legacy::Key::Map(n) => {
 | 
			
		||||
                        Key::Map(self.ops.m.borrow_mut().props.cache(n.to_string()))
 | 
			
		||||
                    }
 | 
			
		||||
                    legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD),
 | 
			
		||||
                    // FIXME dont need to_string()
 | 
			
		||||
                    legacy::Key::Seq(legacy::ElementId::Id(i)) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -727,8 +779,8 @@ impl Automerge {
 | 
			
		|||
        let bytes = encode_document(
 | 
			
		||||
            &c,
 | 
			
		||||
            ops.as_slice(),
 | 
			
		||||
            &self.ops.m.actors,
 | 
			
		||||
            &self.ops.m.props.cache,
 | 
			
		||||
            &self.ops.m.borrow().actors,
 | 
			
		||||
            &self.ops.m.borrow().props.cache,
 | 
			
		||||
        );
 | 
			
		||||
        if bytes.is_ok() {
 | 
			
		||||
            self.saved = self.get_heads().iter().copied().collect();
 | 
			
		||||
| 
						 | 
				
			
			@ -899,7 +951,7 @@ impl Automerge {
 | 
			
		|||
    pub fn get_last_local_change(&mut self) -> Option<&Change> {
 | 
			
		||||
        self.ensure_transaction_closed();
 | 
			
		||||
        if let Some(actor) = &self.actor {
 | 
			
		||||
            let actor = &self.ops.m.actors[*actor];
 | 
			
		||||
            let actor = &self.ops.m.borrow().actors[*actor];
 | 
			
		||||
            return self.history.iter().rev().find(|c| c.actor_id() == actor);
 | 
			
		||||
        }
 | 
			
		||||
        None
 | 
			
		||||
| 
						 | 
				
			
			@ -930,7 +982,13 @@ impl Automerge {
 | 
			
		|||
                        to_see.push(*h);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                let actor = self.ops.m.actors.lookup(c.actor_id().clone()).unwrap();
 | 
			
		||||
                let actor = self
 | 
			
		||||
                    .ops
 | 
			
		||||
                    .m
 | 
			
		||||
                    .borrow()
 | 
			
		||||
                    .actors
 | 
			
		||||
                    .lookup(c.actor_id().clone())
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                clock.include(actor, c.max_op());
 | 
			
		||||
                seen.insert(hash);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1006,7 +1064,13 @@ impl Automerge {
 | 
			
		|||
        let history_index = self.history.len();
 | 
			
		||||
 | 
			
		||||
        self.states
 | 
			
		||||
            .entry(self.ops.m.actors.cache(change.actor_id().clone()))
 | 
			
		||||
            .entry(
 | 
			
		||||
                self.ops
 | 
			
		||||
                    .m
 | 
			
		||||
                    .borrow_mut()
 | 
			
		||||
                    .actors
 | 
			
		||||
                    .cache(change.actor_id().clone()),
 | 
			
		||||
            )
 | 
			
		||||
            .or_default()
 | 
			
		||||
            .push(history_index);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1023,7 +1087,7 @@ impl Automerge {
 | 
			
		|||
        self.deps.insert(change.hash);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn import<I: Importable>(&self, s: &str) -> Result<I, AutomergeError> {
 | 
			
		||||
    pub(crate) fn import<I: Importable>(&self, s: &str) -> Result<I, AutomergeError> {
 | 
			
		||||
        if let Some(x) = I::from(s) {
 | 
			
		||||
            Ok(x)
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -1037,17 +1101,40 @@ impl Automerge {
 | 
			
		|||
            let actor = self
 | 
			
		||||
                .ops
 | 
			
		||||
                .m
 | 
			
		||||
                .borrow()
 | 
			
		||||
                .actors
 | 
			
		||||
                .lookup(actor)
 | 
			
		||||
                .ok_or_else(|| AutomergeError::InvalidOpId(s.to_owned()))?;
 | 
			
		||||
            Ok(I::wrap(OpId(counter, actor)))
 | 
			
		||||
            Ok(I::wrap(InternalOpId::new(counter, actor)))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn export<E: Exportable>(&self, id: E) -> String {
 | 
			
		||||
    fn import_opid(&self, opid: &OpId) -> InternalOpId {
 | 
			
		||||
        self.ops.m.borrow_mut().import_opid(opid)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn export_opid(&self, opid: &InternalOpId) -> Option<OpId> {
 | 
			
		||||
        OpId::from_internal(opid, &self.ops.m.borrow_mut())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn import_objid<'a, A: Into<ObjId<'a>>>(&self, objid: A) -> InternalObjId {
 | 
			
		||||
        match objid.into() {
 | 
			
		||||
            ObjId::Root => InternalObjId::Root,
 | 
			
		||||
            ObjId::Op(external_op) => {
 | 
			
		||||
                let op = self.import_opid(&external_op);
 | 
			
		||||
                InternalObjId::Op(op)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn export<E: Exportable>(&self, id: E) -> String {
 | 
			
		||||
        match id.export() {
 | 
			
		||||
            Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.actors[id.actor()]),
 | 
			
		||||
            Export::Prop(index) => self.ops.m.props[index].clone(),
 | 
			
		||||
            Export::Id(id) => format!(
 | 
			
		||||
                "{}@{}",
 | 
			
		||||
                id.counter(),
 | 
			
		||||
                self.ops.m.borrow().actors[id.actor()]
 | 
			
		||||
            ),
 | 
			
		||||
            Export::Prop(index) => self.ops.m.borrow().props[index].clone(),
 | 
			
		||||
            Export::Special(s) => s,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1066,7 +1153,7 @@ impl Automerge {
 | 
			
		|||
            let id = self.export(i.id);
 | 
			
		||||
            let obj = self.export(i.obj);
 | 
			
		||||
            let key = match i.key {
 | 
			
		||||
                Key::Map(n) => self.ops.m.props[n].clone(),
 | 
			
		||||
                Key::Map(n) => self.ops.m.borrow().props[n].clone(),
 | 
			
		||||
                Key::Seq(n) => self.export(n),
 | 
			
		||||
            };
 | 
			
		||||
            let value: String = match &i.action {
 | 
			
		||||
| 
						 | 
				
			
			@ -1093,6 +1180,16 @@ impl Automerge {
 | 
			
		|||
    pub fn visualise_optree(&self) -> String {
 | 
			
		||||
        self.ops.visualise()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn labelled_value(&self, op: &Op) -> (Value, OpId) {
 | 
			
		||||
        let id = self.export_opid(&op.id).unwrap();
 | 
			
		||||
        let value = match &op.action {
 | 
			
		||||
            OpType::Make(obj_type) => Value::Object(*obj_type),
 | 
			
		||||
            OpType::Set(scalar) => Value::Scalar(scalar.clone()),
 | 
			
		||||
            _ => panic!("expected a make or set op"),
 | 
			
		||||
        };
 | 
			
		||||
        (value, id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
| 
						 | 
				
			
			@ -1123,9 +1220,9 @@ mod tests {
 | 
			
		|||
    fn insert_op() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        doc.set_actor(ActorId::random());
 | 
			
		||||
        doc.set(ROOT, "hello", "world")?;
 | 
			
		||||
        doc.set(ObjId::Root, "hello", "world")?;
 | 
			
		||||
        assert!(doc.pending_ops() == 1);
 | 
			
		||||
        doc.value(ROOT, "hello")?;
 | 
			
		||||
        doc.value(ObjId::Root, "hello")?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1133,17 +1230,20 @@ mod tests {
 | 
			
		|||
    fn test_list() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        doc.set_actor(ActorId::random());
 | 
			
		||||
        let list_id = doc.set(ROOT, "items", Value::list())?.unwrap();
 | 
			
		||||
        doc.set(ROOT, "zzz", "zzzval")?;
 | 
			
		||||
        assert!(doc.value(ROOT, "items")?.unwrap().1 == list_id);
 | 
			
		||||
        doc.insert(list_id, 0, "a")?;
 | 
			
		||||
        doc.insert(list_id, 0, "b")?;
 | 
			
		||||
        doc.insert(list_id, 2, "c")?;
 | 
			
		||||
        doc.insert(list_id, 1, "d")?;
 | 
			
		||||
        assert!(doc.value(list_id, 0)?.unwrap().0 == "b".into());
 | 
			
		||||
        assert!(doc.value(list_id, 1)?.unwrap().0 == "d".into());
 | 
			
		||||
        assert!(doc.value(list_id, 2)?.unwrap().0 == "a".into());
 | 
			
		||||
        assert!(doc.value(list_id, 3)?.unwrap().0 == "c".into());
 | 
			
		||||
        let list_id = doc
 | 
			
		||||
            .set(ObjId::Root, "items", Value::list())?
 | 
			
		||||
            .unwrap()
 | 
			
		||||
            .into();
 | 
			
		||||
        doc.set(ObjId::Root, "zzz", "zzzval")?;
 | 
			
		||||
        assert!(doc.value(ObjId::Root, "items")?.unwrap().1 == list_id);
 | 
			
		||||
        doc.insert(&list_id, 0, "a")?;
 | 
			
		||||
        doc.insert(&list_id, 0, "b")?;
 | 
			
		||||
        doc.insert(&list_id, 2, "c")?;
 | 
			
		||||
        doc.insert(&list_id, 1, "d")?;
 | 
			
		||||
        assert!(doc.value(&list_id, 0)?.unwrap().0 == "b".into());
 | 
			
		||||
        assert!(doc.value(&list_id, 1)?.unwrap().0 == "d".into());
 | 
			
		||||
        assert!(doc.value(&list_id, 2)?.unwrap().0 == "a".into());
 | 
			
		||||
        assert!(doc.value(&list_id, 3)?.unwrap().0 == "c".into());
 | 
			
		||||
        assert!(doc.length(list_id) == 4);
 | 
			
		||||
        doc.save()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
| 
						 | 
				
			
			@ -1153,22 +1253,24 @@ mod tests {
 | 
			
		|||
    fn test_del() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        doc.set_actor(ActorId::random());
 | 
			
		||||
        doc.set(ROOT, "xxx", "xxx")?;
 | 
			
		||||
        assert!(!doc.values(ROOT, "xxx")?.is_empty());
 | 
			
		||||
        doc.del(ROOT, "xxx")?;
 | 
			
		||||
        assert!(doc.values(ROOT, "xxx")?.is_empty());
 | 
			
		||||
        doc.set(ObjId::Root, "xxx", "xxx")?;
 | 
			
		||||
        assert!(!doc.values(ObjId::Root, "xxx")?.is_empty());
 | 
			
		||||
        doc.del(ObjId::Root, "xxx")?;
 | 
			
		||||
        assert!(doc.values(ObjId::Root, "xxx")?.is_empty());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_inc() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        let id = doc.set(ROOT, "counter", Value::counter(10))?.unwrap();
 | 
			
		||||
        assert!(doc.value(ROOT, "counter")? == Some((Value::counter(10), id)));
 | 
			
		||||
        doc.inc(ROOT, "counter", 10)?;
 | 
			
		||||
        assert!(doc.value(ROOT, "counter")? == Some((Value::counter(20), id)));
 | 
			
		||||
        doc.inc(ROOT, "counter", -5)?;
 | 
			
		||||
        assert!(doc.value(ROOT, "counter")? == Some((Value::counter(15), id)));
 | 
			
		||||
        let id = doc
 | 
			
		||||
            .set(ObjId::Root, "counter", Value::counter(10))?
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(10), id.clone())));
 | 
			
		||||
        doc.inc(ObjId::Root, "counter", 10)?;
 | 
			
		||||
        assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(20), id.clone())));
 | 
			
		||||
        doc.inc(ObjId::Root, "counter", -5)?;
 | 
			
		||||
        assert!(doc.value(ObjId::Root, "counter")? == Some((Value::counter(15), id.clone())));
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1176,15 +1278,15 @@ mod tests {
 | 
			
		|||
    fn test_save_incremental() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
 | 
			
		||||
        doc.set(ROOT, "foo", 1)?;
 | 
			
		||||
        doc.set(ObjId::Root, "foo", 1)?;
 | 
			
		||||
 | 
			
		||||
        let save1 = doc.save().unwrap();
 | 
			
		||||
 | 
			
		||||
        doc.set(ROOT, "bar", 2)?;
 | 
			
		||||
        doc.set(ObjId::Root, "bar", 2)?;
 | 
			
		||||
 | 
			
		||||
        let save2 = doc.save_incremental();
 | 
			
		||||
 | 
			
		||||
        doc.set(ROOT, "baz", 3)?;
 | 
			
		||||
        doc.set(ObjId::Root, "baz", 3)?;
 | 
			
		||||
 | 
			
		||||
        let save3 = doc.save_incremental();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1202,7 +1304,7 @@ mod tests {
 | 
			
		|||
        let mut doc_a = Automerge::load(&save_a)?;
 | 
			
		||||
        let mut doc_b = Automerge::load(&save_b)?;
 | 
			
		||||
 | 
			
		||||
        assert!(doc_a.values(ROOT, "baz")? == doc_b.values(ROOT, "baz")?);
 | 
			
		||||
        assert!(doc_a.values(ObjId::Root, "baz")? == doc_b.values(ObjId::Root, "baz")?);
 | 
			
		||||
 | 
			
		||||
        assert!(doc_a.save().unwrap() == doc_b.save().unwrap());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1212,17 +1314,17 @@ mod tests {
 | 
			
		|||
    #[test]
 | 
			
		||||
    fn test_save_text() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        let text = doc.set(ROOT, "text", Value::text())?.unwrap();
 | 
			
		||||
        let text = doc.set(ObjId::Root, "text", Value::text())?.unwrap();
 | 
			
		||||
        let heads1 = doc.commit(None, None);
 | 
			
		||||
        doc.splice_text(text, 0, 0, "hello world")?;
 | 
			
		||||
        doc.splice_text(&text, 0, 0, "hello world")?;
 | 
			
		||||
        let heads2 = doc.commit(None, None);
 | 
			
		||||
        doc.splice_text(text, 6, 0, "big bad ")?;
 | 
			
		||||
        doc.splice_text(&text, 6, 0, "big bad ")?;
 | 
			
		||||
        let heads3 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        assert!(&doc.text(text)? == "hello big bad world");
 | 
			
		||||
        assert!(&doc.text_at(text, &heads1)?.is_empty());
 | 
			
		||||
        assert!(&doc.text_at(text, &heads2)? == "hello world");
 | 
			
		||||
        assert!(&doc.text_at(text, &heads3)? == "hello big bad world");
 | 
			
		||||
        assert!(&doc.text(&text)? == "hello big bad world");
 | 
			
		||||
        assert!(&doc.text_at(&text, &heads1)?.is_empty());
 | 
			
		||||
        assert!(&doc.text_at(&text, &heads2)? == "hello world");
 | 
			
		||||
        assert!(&doc.text_at(&text, &heads3)? == "hello big bad world");
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1231,50 +1333,50 @@ mod tests {
 | 
			
		|||
    fn test_props_vals_at() -> Result<(), AutomergeError> {
 | 
			
		||||
        let mut doc = Automerge::new();
 | 
			
		||||
        doc.set_actor("aaaa".try_into().unwrap());
 | 
			
		||||
        doc.set(ROOT, "prop1", "val1")?;
 | 
			
		||||
        doc.set(ObjId::Root, "prop1", "val1")?;
 | 
			
		||||
        doc.commit(None, None);
 | 
			
		||||
        let heads1 = doc.get_heads();
 | 
			
		||||
        doc.set(ROOT, "prop1", "val2")?;
 | 
			
		||||
        doc.set(ObjId::Root, "prop1", "val2")?;
 | 
			
		||||
        doc.commit(None, None);
 | 
			
		||||
        let heads2 = doc.get_heads();
 | 
			
		||||
        doc.set(ROOT, "prop2", "val3")?;
 | 
			
		||||
        doc.set(ObjId::Root, "prop2", "val3")?;
 | 
			
		||||
        doc.commit(None, None);
 | 
			
		||||
        let heads3 = doc.get_heads();
 | 
			
		||||
        doc.del(ROOT, "prop1")?;
 | 
			
		||||
        doc.del(ObjId::Root, "prop1")?;
 | 
			
		||||
        doc.commit(None, None);
 | 
			
		||||
        let heads4 = doc.get_heads();
 | 
			
		||||
        doc.set(ROOT, "prop3", "val4")?;
 | 
			
		||||
        doc.set(ObjId::Root, "prop3", "val4")?;
 | 
			
		||||
        doc.commit(None, None);
 | 
			
		||||
        let heads5 = doc.get_heads();
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &heads1) == vec!["prop1".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &heads1)?.unwrap().0 == Value::str("val1"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &heads1)? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &heads1)? == None);
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &heads1) == vec!["prop1".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &heads1)?.unwrap().0 == Value::str("val1"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &heads1)? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &heads1)? == None);
 | 
			
		||||
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &heads2) == vec!["prop1".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &heads2)?.unwrap().0 == Value::str("val2"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &heads2)? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &heads2)? == None);
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &heads2) == vec!["prop1".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &heads2)?.unwrap().0 == Value::str("val2"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &heads2)? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &heads2)? == None);
 | 
			
		||||
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &heads3)?.unwrap().0 == Value::str("val2"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &heads3)? == None);
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &heads3)?.unwrap().0 == Value::str("val2"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &heads3)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &heads3)? == None);
 | 
			
		||||
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &heads4) == vec!["prop2".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &heads4)? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &heads4)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &heads4)? == None);
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &heads4) == vec!["prop2".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &heads4)? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &heads4)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &heads4)? == None);
 | 
			
		||||
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &heads5)? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &heads5)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &heads5)?.unwrap().0 == Value::str("val4"));
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &heads5)? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &heads5)?.unwrap().0 == Value::str("val3"));
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &heads5)?.unwrap().0 == Value::str("val4"));
 | 
			
		||||
 | 
			
		||||
        assert!(doc.keys_at(ROOT, &[]).is_empty());
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop1", &[])? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop2", &[])? == None);
 | 
			
		||||
        assert!(doc.value_at(ROOT, "prop3", &[])? == None);
 | 
			
		||||
        assert!(doc.keys_at(ObjId::Root, &[]).is_empty());
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop1", &[])? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop2", &[])? == None);
 | 
			
		||||
        assert!(doc.value_at(ObjId::Root, "prop3", &[])? == None);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1283,47 +1385,47 @@ mod tests {
 | 
			
		|||
        let mut doc = Automerge::new();
 | 
			
		||||
        doc.set_actor("aaaa".try_into().unwrap());
 | 
			
		||||
 | 
			
		||||
        let list = doc.set(ROOT, "list", Value::list())?.unwrap();
 | 
			
		||||
        let list = doc.set(ObjId::Root, "list", Value::list())?.unwrap();
 | 
			
		||||
        let heads1 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        doc.insert(list, 0, Value::int(10))?;
 | 
			
		||||
        doc.insert(&list, 0, Value::int(10))?;
 | 
			
		||||
        let heads2 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        doc.set(list, 0, Value::int(20))?;
 | 
			
		||||
        doc.insert(list, 0, Value::int(30))?;
 | 
			
		||||
        doc.set(&list, 0, Value::int(20))?;
 | 
			
		||||
        doc.insert(&list, 0, Value::int(30))?;
 | 
			
		||||
        let heads3 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        doc.set(list, 1, Value::int(40))?;
 | 
			
		||||
        doc.insert(list, 1, Value::int(50))?;
 | 
			
		||||
        doc.set(&list, 1, Value::int(40))?;
 | 
			
		||||
        doc.insert(&list, 1, Value::int(50))?;
 | 
			
		||||
        let heads4 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        doc.del(list, 2)?;
 | 
			
		||||
        doc.del(&list, 2)?;
 | 
			
		||||
        let heads5 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        doc.del(list, 0)?;
 | 
			
		||||
        doc.del(&list, 0)?;
 | 
			
		||||
        let heads6 = doc.commit(None, None);
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads1) == 0);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads1)?.is_none());
 | 
			
		||||
        assert!(doc.length_at(&list, &heads1) == 0);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads1)?.is_none());
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads2) == 1);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads2)?.unwrap().0 == Value::int(10));
 | 
			
		||||
        assert!(doc.length_at(&list, &heads2) == 1);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads2)?.unwrap().0 == Value::int(10));
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads3) == 2);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads3)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(list, 1, &heads3)?.unwrap().0 == Value::int(20));
 | 
			
		||||
        assert!(doc.length_at(&list, &heads3) == 2);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads3)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(&list, 1, &heads3)?.unwrap().0 == Value::int(20));
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads4) == 3);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads4)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(list, 1, &heads4)?.unwrap().0 == Value::int(50));
 | 
			
		||||
        assert!(doc.value_at(list, 2, &heads4)?.unwrap().0 == Value::int(40));
 | 
			
		||||
        assert!(doc.length_at(&list, &heads4) == 3);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads4)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(&list, 1, &heads4)?.unwrap().0 == Value::int(50));
 | 
			
		||||
        assert!(doc.value_at(&list, 2, &heads4)?.unwrap().0 == Value::int(40));
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads5) == 2);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads5)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(list, 1, &heads5)?.unwrap().0 == Value::int(50));
 | 
			
		||||
        assert!(doc.length_at(&list, &heads5) == 2);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads5)?.unwrap().0 == Value::int(30));
 | 
			
		||||
        assert!(doc.value_at(&list, 1, &heads5)?.unwrap().0 == Value::int(50));
 | 
			
		||||
 | 
			
		||||
        assert!(doc.length_at(list, &heads6) == 1);
 | 
			
		||||
        assert!(doc.value_at(list, 0, &heads6)?.unwrap().0 == Value::int(50));
 | 
			
		||||
        assert!(doc.length_at(&list, &heads6) == 1);
 | 
			
		||||
        assert!(doc.value_at(&list, 0, &heads6)?.unwrap().0 == Value::int(50));
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,13 @@
 | 
			
		|||
use crate::op_tree::OpTreeInternal;
 | 
			
		||||
use crate::query::TreeQuery;
 | 
			
		||||
use crate::{ActorId, IndexedCache, Key, ObjId, Op, OpId};
 | 
			
		||||
use crate::{ActorId, IndexedCache, Key, types::{ObjId, OpId}, Op};
 | 
			
		||||
use crate::external_types::ExternalOpId;
 | 
			
		||||
use fxhash::FxBuildHasher;
 | 
			
		||||
use std::cmp::Ordering;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::rc::Rc;
 | 
			
		||||
use std::cell::RefCell;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
pub(crate) type OpSet = OpSetInternal<16>;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +16,7 @@ pub(crate) struct OpSetInternal<const B: usize> {
 | 
			
		|||
    trees: HashMap<ObjId, OpTreeInternal<B>, FxBuildHasher>,
 | 
			
		||||
    objs: Vec<ObjId>,
 | 
			
		||||
    length: usize,
 | 
			
		||||
    pub m: OpSetMetadata,
 | 
			
		||||
    pub m: Rc<RefCell<OpSetMetadata>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<const B: usize> OpSetInternal<B> {
 | 
			
		||||
| 
						 | 
				
			
			@ -21,10 +25,11 @@ impl<const B: usize> OpSetInternal<B> {
 | 
			
		|||
            trees: Default::default(),
 | 
			
		||||
            objs: Default::default(),
 | 
			
		||||
            length: 0,
 | 
			
		||||
            m: OpSetMetadata {
 | 
			
		||||
            m: Rc::new(RefCell::new(OpSetMetadata {
 | 
			
		||||
                actors: IndexedCache::new(),
 | 
			
		||||
                props: IndexedCache::new(),
 | 
			
		||||
            },
 | 
			
		||||
                last_opid: None,
 | 
			
		||||
            })),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +46,7 @@ impl<const B: usize> OpSetInternal<B> {
 | 
			
		|||
        Q: TreeQuery<B>,
 | 
			
		||||
    {
 | 
			
		||||
        if let Some(tree) = self.trees.get(&obj) {
 | 
			
		||||
            tree.search(query, &self.m)
 | 
			
		||||
            tree.search(query, &*self.m.borrow())
 | 
			
		||||
        } else {
 | 
			
		||||
            query
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +88,7 @@ impl<const B: usize> OpSetInternal<B> {
 | 
			
		|||
            .entry(element.obj)
 | 
			
		||||
            .or_insert_with(|| {
 | 
			
		||||
                let pos = objs
 | 
			
		||||
                    .binary_search_by(|probe| m.lamport_cmp(probe.0, element.obj.0))
 | 
			
		||||
                    .binary_search_by(|probe| m.borrow().lamport_cmp(probe, &element.obj))
 | 
			
		||||
                    .unwrap_err();
 | 
			
		||||
                objs.insert(pos, element.obj);
 | 
			
		||||
                Default::default()
 | 
			
		||||
| 
						 | 
				
			
			@ -148,12 +153,16 @@ impl<'a, const B: usize> Iterator for Iter<'a, B> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub(crate) struct OpSetMetadata {
 | 
			
		||||
    pub actors: IndexedCache<ActorId>,
 | 
			
		||||
    pub props: IndexedCache<String>,
 | 
			
		||||
    // For the common case of many subsequent operations on the same object we cache the last
 | 
			
		||||
    // object we looked up
 | 
			
		||||
    last_opid: Option<(ExternalOpId, OpId)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
impl OpSetMetadata {
 | 
			
		||||
    pub fn key_cmp(&self, left: &Key, right: &Key) -> Ordering {
 | 
			
		||||
        match (left, right) {
 | 
			
		||||
| 
						 | 
				
			
			@ -162,14 +171,54 @@ impl OpSetMetadata {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn lamport_cmp(&self, left: OpId, right: OpId) -> Ordering {
 | 
			
		||||
        match (left, right) {
 | 
			
		||||
            (OpId(0, _), OpId(0, _)) => Ordering::Equal,
 | 
			
		||||
            (OpId(0, _), OpId(_, _)) => Ordering::Less,
 | 
			
		||||
            (OpId(_, _), OpId(0, _)) => Ordering::Greater,
 | 
			
		||||
            // FIXME - this one seems backwards to me - why - is values() returning in the wrong order?
 | 
			
		||||
            (OpId(a, x), OpId(b, y)) if a == b => self.actors[y].cmp(&self.actors[x]),
 | 
			
		||||
            (OpId(a, _), OpId(b, _)) => a.cmp(&b),
 | 
			
		||||
    pub fn lamport_cmp<S: SuccinctLamport>(&self, left: S, right: S) -> Ordering {
 | 
			
		||||
        S::cmp(self, left, right)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn import_opid(&mut self, ext_opid: &ExternalOpId) -> OpId {
 | 
			
		||||
        if let Some((last_ext, last_int)) = &self.last_opid {
 | 
			
		||||
            if last_ext == ext_opid {
 | 
			
		||||
                return *last_int;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        let actor = self.actors.cache(ext_opid.actor().clone());
 | 
			
		||||
        let opid = OpId::new(ext_opid.counter(), actor);
 | 
			
		||||
        self.last_opid = Some((ext_opid.clone(), opid));
 | 
			
		||||
        opid
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Lamport timestamps which don't contain their actor ID directly and therefore need access to
 | 
			
		||||
/// some metadata to compare their actor ID parts
 | 
			
		||||
pub(crate) trait SuccinctLamport {
 | 
			
		||||
    fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SuccinctLamport for OpId {
 | 
			
		||||
    fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering {
 | 
			
		||||
        match (left.counter(), right.counter()) {
 | 
			
		||||
            (0, 0) => Ordering::Equal,
 | 
			
		||||
            (0, _) => Ordering::Less,
 | 
			
		||||
            (_, 0) => Ordering::Greater,
 | 
			
		||||
            (a, b) if a == b => m.actors[right.actor()].cmp(&m.actors[left.actor()]),
 | 
			
		||||
            (a, b) => a.cmp(&b),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SuccinctLamport for ObjId {
 | 
			
		||||
    fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering {
 | 
			
		||||
        match (left, right) {
 | 
			
		||||
            (ObjId::Root, ObjId::Root) => Ordering::Equal,
 | 
			
		||||
            (ObjId::Root, ObjId::Op(_)) => Ordering::Less,
 | 
			
		||||
            (ObjId::Op(_), ObjId::Root) => Ordering::Greater,
 | 
			
		||||
            (ObjId::Op(left_op), ObjId::Op(right_op)) => <OpId as SuccinctLamport>::cmp(m, left_op, right_op),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SuccinctLamport for &ObjId {
 | 
			
		||||
    fn cmp(m: &OpSetMetadata, left: Self, right: Self) -> Ordering {
 | 
			
		||||
        <ObjId as SuccinctLamport>::cmp(m, *left, *right)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ use std::{
 | 
			
		|||
 | 
			
		||||
pub(crate) use crate::op_set::OpSetMetadata;
 | 
			
		||||
use crate::query::{Index, QueryResult, TreeQuery};
 | 
			
		||||
use crate::{Op, OpId};
 | 
			
		||||
use crate::types::{Op, OpId};
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
| 
						 | 
				
			
			@ -628,12 +628,12 @@ struct CounterData {
 | 
			
		|||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use crate::legacy as amp;
 | 
			
		||||
    use crate::{Op, OpId};
 | 
			
		||||
    use crate::types::{Op, OpId};
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    fn op(n: usize) -> Op {
 | 
			
		||||
        let zero = OpId(0, 0);
 | 
			
		||||
        let zero = OpId::new(0, 0);
 | 
			
		||||
        Op {
 | 
			
		||||
            change: n,
 | 
			
		||||
            id: zero,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
 | 
			
		||||
use crate::{Clock, ElemId, Op, OpId, OpType, ScalarValue};
 | 
			
		||||
use crate::{Clock, ElemId, Op, ScalarValue, types::{OpId, OpType}};
 | 
			
		||||
use fxhash::FxBuildHasher;
 | 
			
		||||
use std::cmp::Ordering;
 | 
			
		||||
use std::collections::{HashMap, HashSet};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use crate::op_tree::OpTreeNode;
 | 
			
		||||
use crate::query::{QueryResult, TreeQuery};
 | 
			
		||||
use crate::ObjId;
 | 
			
		||||
use crate::types::ObjId;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
 | 
			
		||||
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
 | 
			
		||||
use crate::{ElemId, ObjId, Op};
 | 
			
		||||
use crate::{ElemId, types::ObjId, Op};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ impl<const B: usize> TreeQuery<B> for ListVals {
 | 
			
		|||
        child: &OpTreeNode<B>,
 | 
			
		||||
        m: &OpSetMetadata,
 | 
			
		||||
    ) -> QueryResult {
 | 
			
		||||
        let start = binary_search_by(child, |op| m.lamport_cmp(op.obj.0, self.obj.0));
 | 
			
		||||
        let start = binary_search_by(child, |op| m.lamport_cmp(op.obj, self.obj));
 | 
			
		||||
        let mut counters = Default::default();
 | 
			
		||||
        for pos in start..child.len() {
 | 
			
		||||
            let op = child.get(pos).unwrap();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use crate::op_tree::{OpSetMetadata, OpTreeNode};
 | 
			
		||||
use crate::query::{binary_search_by, is_visible, visible_op, QueryResult, TreeQuery};
 | 
			
		||||
use crate::{Key, ObjId, Op};
 | 
			
		||||
use crate::{Key, types::ObjId, Op};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ impl<const B: usize> TreeQuery<B> for Prop {
 | 
			
		|||
        m: &OpSetMetadata,
 | 
			
		||||
    ) -> QueryResult {
 | 
			
		||||
        let start = binary_search_by(child, |op| {
 | 
			
		||||
            m.lamport_cmp(op.obj.0, self.obj.0)
 | 
			
		||||
            m.lamport_cmp(op.obj, self.obj)
 | 
			
		||||
                .then_with(|| m.key_cmp(&op.key, &self.key))
 | 
			
		||||
        });
 | 
			
		||||
        let mut counters = Default::default();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ use std::str::FromStr;
 | 
			
		|||
use tinyvec::{ArrayVec, TinyVec};
 | 
			
		||||
 | 
			
		||||
pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0));
 | 
			
		||||
pub const ROOT: OpId = OpId(0, 0);
 | 
			
		||||
 | 
			
		||||
const ROOT_STR: &str = "_root";
 | 
			
		||||
const HEAD_STR: &str = "_head";
 | 
			
		||||
| 
						 | 
				
			
			@ -161,17 +160,17 @@ pub enum OpType {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum Export {
 | 
			
		||||
pub(crate) enum Export {
 | 
			
		||||
    Id(OpId),
 | 
			
		||||
    Special(String),
 | 
			
		||||
    Prop(usize),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Exportable {
 | 
			
		||||
pub(crate) trait Exportable {
 | 
			
		||||
    fn export(&self) -> Export;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Importable {
 | 
			
		||||
pub(crate) trait Importable {
 | 
			
		||||
    fn wrap(id: OpId) -> Self;
 | 
			
		||||
    fn from(s: &str) -> Option<Self>
 | 
			
		||||
    where
 | 
			
		||||
| 
						 | 
				
			
			@ -179,33 +178,32 @@ pub trait Importable {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl OpId {
 | 
			
		||||
    pub(crate) fn new(counter: u64, actor: usize) -> OpId {
 | 
			
		||||
        OpId(counter, actor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub fn counter(&self) -> u64 {
 | 
			
		||||
        self.0
 | 
			
		||||
    }
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub fn actor(&self) -> usize {
 | 
			
		||||
    pub(crate) fn actor(&self) -> usize {
 | 
			
		||||
        self.1
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Exportable for ObjId {
 | 
			
		||||
    fn export(&self) -> Export {
 | 
			
		||||
        if self.0 == ROOT {
 | 
			
		||||
            Export::Special(ROOT_STR.to_owned())
 | 
			
		||||
        } else {
 | 
			
		||||
            Export::Id(self.0)
 | 
			
		||||
        match self {
 | 
			
		||||
            ObjId::Root => Export::Special(ROOT_STR.to_owned()),
 | 
			
		||||
            ObjId::Op(o) => Export::Id(*o)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Exportable for &ObjId {
 | 
			
		||||
    fn export(&self) -> Export {
 | 
			
		||||
        if self.0 == ROOT {
 | 
			
		||||
            Export::Special(ROOT_STR.to_owned())
 | 
			
		||||
        } else {
 | 
			
		||||
            Export::Id(self.0)
 | 
			
		||||
        }
 | 
			
		||||
        (*self).export()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -236,11 +234,11 @@ impl Exportable for Key {
 | 
			
		|||
 | 
			
		||||
impl Importable for ObjId {
 | 
			
		||||
    fn wrap(id: OpId) -> Self {
 | 
			
		||||
        ObjId(id)
 | 
			
		||||
        ObjId::Op(id)
 | 
			
		||||
    }
 | 
			
		||||
    fn from(s: &str) -> Option<Self> {
 | 
			
		||||
        if s == ROOT_STR {
 | 
			
		||||
            Some(ROOT.into())
 | 
			
		||||
            Some(ObjId::Root)
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -251,12 +249,8 @@ impl Importable for OpId {
 | 
			
		|||
    fn wrap(id: OpId) -> Self {
 | 
			
		||||
        id
 | 
			
		||||
    }
 | 
			
		||||
    fn from(s: &str) -> Option<Self> {
 | 
			
		||||
        if s == ROOT_STR {
 | 
			
		||||
            Some(ROOT)
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    fn from(_s: &str) -> Option<Self> {
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +269,10 @@ impl Importable for ElemId {
 | 
			
		|||
 | 
			
		||||
impl From<OpId> for ObjId {
 | 
			
		||||
    fn from(o: OpId) -> Self {
 | 
			
		||||
        ObjId(o)
 | 
			
		||||
        match (o.counter(), o.actor()) {
 | 
			
		||||
            (0,0) => ObjId::Root,
 | 
			
		||||
            (_,_) => ObjId::Op(o),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -352,10 +349,19 @@ impl Key {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)]
 | 
			
		||||
pub struct OpId(pub u64, pub usize);
 | 
			
		||||
pub(crate) struct OpId(u64, usize);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
 | 
			
		||||
pub(crate) struct ObjId(pub OpId);
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash)]
 | 
			
		||||
pub(crate) enum ObjId{
 | 
			
		||||
    Root,
 | 
			
		||||
    Op(OpId),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ObjId {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Root
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
 | 
			
		||||
pub(crate) struct ElemId(pub OpId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use crate::{error, ObjType, Op, OpId, OpType};
 | 
			
		||||
use crate::{error, ObjType, Op, types::OpId, OpType};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use smol_str::SmolStr;
 | 
			
		||||
use std::convert::TryFrom;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
use automerge::ObjId;
 | 
			
		||||
 | 
			
		||||
use std::{collections::HashMap, convert::TryInto, hash::Hash};
 | 
			
		||||
 | 
			
		||||
use serde::ser::{SerializeMap, SerializeSeq};
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +25,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
 | 
			
		|||
 | 
			
		||||
/// This macro makes it easy to make assertions about a document. It is called with two arguments,
 | 
			
		||||
/// the first is a reference to an `automerge::Automerge`, the second is an instance of
 | 
			
		||||
/// `RealizedObject<ExportableOpId>`.
 | 
			
		||||
/// `RealizedObject`.
 | 
			
		||||
///
 | 
			
		||||
/// What - I hear you ask - is a `RealizedObject`? It's a fully hydrated version of the contents of
 | 
			
		||||
/// an automerge document. You don't need to think about this too much though because you can
 | 
			
		||||
| 
						 | 
				
			
			@ -65,29 +67,17 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) {
 | 
			
		|||
///     map!{
 | 
			
		||||
///         "field" => {
 | 
			
		||||
///             op1 => "one",
 | 
			
		||||
///             op2.translate(&doc2) => "two"
 | 
			
		||||
///             op2 => "two"
 | 
			
		||||
///         }
 | 
			
		||||
///     }
 | 
			
		||||
/// );
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// ## Translating OpIds
 | 
			
		||||
///
 | 
			
		||||
/// One thing you may have noticed in the example above is the `op2.translate(&doc2)` call. What is
 | 
			
		||||
/// that doing there? Well, the problem is that automerge OpIDs (in the current API) are specific
 | 
			
		||||
/// to a document. Using an opid from one document in a different document will not work. Therefore
 | 
			
		||||
/// this module defines an `OpIdExt` trait with a `translate` method on it. This method takes a
 | 
			
		||||
/// document and converts the opid into something which knows how to be compared with opids from
 | 
			
		||||
/// another document by using the document you pass to `translate`. Again, all you really need to
 | 
			
		||||
/// know is that when constructing a document for comparison you should call `translate(fromdoc)`
 | 
			
		||||
/// on opids which come from a document other than the one you pass to `assert_doc`.
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! assert_doc {
 | 
			
		||||
    ($doc: expr, $expected: expr) => {{
 | 
			
		||||
        use $crate::helpers::{realize, ExportableOpId};
 | 
			
		||||
        use $crate::helpers::realize;
 | 
			
		||||
        let realized = realize($doc);
 | 
			
		||||
        let to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
 | 
			
		||||
        let exported = to_export.export($doc);
 | 
			
		||||
        let exported: RealizedObject = $expected.into();
 | 
			
		||||
        if realized != exported {
 | 
			
		||||
            let serde_right = serde_json::to_string_pretty(&realized).unwrap();
 | 
			
		||||
            let serde_left = serde_json::to_string_pretty(&exported).unwrap();
 | 
			
		||||
| 
						 | 
				
			
			@ -105,10 +95,9 @@ macro_rules! assert_doc {
 | 
			
		|||
#[macro_export]
 | 
			
		||||
macro_rules! assert_obj {
 | 
			
		||||
    ($doc: expr, $obj_id: expr, $prop: expr, $expected: expr) => {{
 | 
			
		||||
        use $crate::helpers::{realize_prop, ExportableOpId};
 | 
			
		||||
        use $crate::helpers::realize_prop;
 | 
			
		||||
        let realized = realize_prop($doc, $obj_id, $prop);
 | 
			
		||||
        let to_export: RealizedObject<ExportableOpId<'_>> = $expected.into();
 | 
			
		||||
        let exported = to_export.export($doc);
 | 
			
		||||
        let exported: RealizedObject = $expected.into();
 | 
			
		||||
        if realized != exported {
 | 
			
		||||
            let serde_right = serde_json::to_string_pretty(&realized).unwrap();
 | 
			
		||||
            let serde_left = serde_json::to_string_pretty(&exported).unwrap();
 | 
			
		||||
| 
						 | 
				
			
			@ -145,9 +134,9 @@ macro_rules! map {
 | 
			
		|||
    (@inner { $($opid:expr => $value:expr),* }) => {
 | 
			
		||||
        {
 | 
			
		||||
            use std::collections::HashMap;
 | 
			
		||||
            let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
 | 
			
		||||
            let mut inner: HashMap<ObjId, RealizedObject> = HashMap::new();
 | 
			
		||||
            $(
 | 
			
		||||
                let _ = inner.insert($opid.into(), $value.into());
 | 
			
		||||
                let _ = inner.insert(ObjId::from((&$opid)).into_owned(), $value.into());
 | 
			
		||||
            )*
 | 
			
		||||
            inner
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -159,9 +148,8 @@ macro_rules! map {
 | 
			
		|||
    ($($key:expr => $inner:tt),*) => {
 | 
			
		||||
        {
 | 
			
		||||
            use std::collections::HashMap;
 | 
			
		||||
            use crate::helpers::ExportableOpId;
 | 
			
		||||
            let _cap = map!(@count $($key),*);
 | 
			
		||||
            let mut _map: HashMap<String, HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>>> = ::std::collections::HashMap::with_capacity(_cap);
 | 
			
		||||
            let mut _map: HashMap<String, HashMap<ObjId, RealizedObject>> = ::std::collections::HashMap::with_capacity(_cap);
 | 
			
		||||
            $(
 | 
			
		||||
                let inner = map!(@inner $inner);
 | 
			
		||||
                let _ = _map.insert($key.to_string(), inner);
 | 
			
		||||
| 
						 | 
				
			
			@ -194,9 +182,9 @@ macro_rules! list {
 | 
			
		|||
    (@inner { $($opid:expr => $value:expr),* }) => {
 | 
			
		||||
        {
 | 
			
		||||
            use std::collections::HashMap;
 | 
			
		||||
            let mut inner: HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>> = HashMap::new();
 | 
			
		||||
            let mut inner: HashMap<ObjId, RealizedObject> = HashMap::new();
 | 
			
		||||
            $(
 | 
			
		||||
                let _ = inner.insert($opid.into(), $value.into());
 | 
			
		||||
                let _ = inner.insert(ObjId::from(&$opid).into_owned(), $value.into());
 | 
			
		||||
            )*
 | 
			
		||||
            inner
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -204,9 +192,8 @@ macro_rules! list {
 | 
			
		|||
    ($($inner:tt,)+) => { list!($($inner),+) };
 | 
			
		||||
    ($($inner:tt),*) => {
 | 
			
		||||
        {
 | 
			
		||||
            use crate::helpers::ExportableOpId;
 | 
			
		||||
            let _cap = list!(@count $($inner),*);
 | 
			
		||||
            let mut _list: Vec<HashMap<ExportableOpId<'_>, RealizedObject<ExportableOpId<'_>>>> = Vec::new();
 | 
			
		||||
            let mut _list: Vec<HashMap<ObjId, RealizedObject>> = Vec::new();
 | 
			
		||||
            $(
 | 
			
		||||
                //println!("{}", stringify!($inner));
 | 
			
		||||
                let inner = list!(@inner $inner);
 | 
			
		||||
| 
						 | 
				
			
			@ -217,26 +204,6 @@ macro_rules! list {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Translate an op ID produced by one document to an op ID which can be understood by
 | 
			
		||||
/// another
 | 
			
		||||
///
 | 
			
		||||
/// The current API of automerge exposes OpIds of the form (u64, usize) where the first component
 | 
			
		||||
/// is the counter of an actors lamport timestamp and the second component is the index into an
 | 
			
		||||
/// array of actor IDs stored by the document where the opid was generated. Obviously this is not
 | 
			
		||||
/// portable between documents as the index of the actor array is unlikely to match between two
 | 
			
		||||
/// documents. This function translates between the two representations.
 | 
			
		||||
///
 | 
			
		||||
/// At some point we will probably change the API to not be document specific but this function
 | 
			
		||||
/// allows us to write tests first.
 | 
			
		||||
pub fn translate_obj_id(
 | 
			
		||||
    from: &automerge::Automerge,
 | 
			
		||||
    to: &automerge::Automerge,
 | 
			
		||||
    id: automerge::OpId,
 | 
			
		||||
) -> automerge::OpId {
 | 
			
		||||
    let exported = from.export(id);
 | 
			
		||||
    to.import(&exported).unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn mk_counter(value: i64) -> automerge::ScalarValue {
 | 
			
		||||
    automerge::ScalarValue::Counter(value)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -253,13 +220,13 @@ impl std::fmt::Display for ExportedOpId {
 | 
			
		|||
/// A `RealizedObject` is a representation of all the current values in a document - including
 | 
			
		||||
/// conflicts.
 | 
			
		||||
#[derive(PartialEq, Debug)]
 | 
			
		||||
pub enum RealizedObject<Oid: PartialEq + Eq + Hash> {
 | 
			
		||||
    Map(HashMap<String, HashMap<Oid, RealizedObject<Oid>>>),
 | 
			
		||||
    Sequence(Vec<HashMap<Oid, RealizedObject<Oid>>>),
 | 
			
		||||
pub enum RealizedObject<'a> {
 | 
			
		||||
    Map(HashMap<String, HashMap<ObjId<'a>, RealizedObject<'a>>>),
 | 
			
		||||
    Sequence(Vec<HashMap<ObjId<'a>, RealizedObject<'a>>>),
 | 
			
		||||
    Value(automerge::ScalarValue),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl serde::Serialize for RealizedObject<ExportedOpId> {
 | 
			
		||||
impl serde::Serialize for RealizedObject<'static> {
 | 
			
		||||
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
			
		||||
    where
 | 
			
		||||
        S: serde::Serializer,
 | 
			
		||||
| 
						 | 
				
			
			@ -271,7 +238,7 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
 | 
			
		|||
                    let kvs_serded = kvs
 | 
			
		||||
                        .iter()
 | 
			
		||||
                        .map(|(opid, value)| (opid.to_string(), value))
 | 
			
		||||
                        .collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
 | 
			
		||||
                        .collect::<HashMap<String, &RealizedObject>>();
 | 
			
		||||
                    map_ser.serialize_entry(k, &kvs_serded)?;
 | 
			
		||||
                }
 | 
			
		||||
                map_ser.end()
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +249,7 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
 | 
			
		|||
                    let kvs_serded = elem
 | 
			
		||||
                        .iter()
 | 
			
		||||
                        .map(|(opid, value)| (opid.to_string(), value))
 | 
			
		||||
                        .collect::<HashMap<String, &RealizedObject<ExportedOpId>>>();
 | 
			
		||||
                        .collect::<HashMap<String, &RealizedObject>>();
 | 
			
		||||
                    list_ser.serialize_element(&kvs_serded)?;
 | 
			
		||||
                }
 | 
			
		||||
                list_ser.end()
 | 
			
		||||
| 
						 | 
				
			
			@ -292,40 +259,40 @@ impl serde::Serialize for RealizedObject<ExportedOpId> {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn realize(doc: &automerge::Automerge) -> RealizedObject<ExportedOpId> {
 | 
			
		||||
    realize_obj(doc, automerge::ROOT, automerge::ObjType::Map)
 | 
			
		||||
pub fn realize<'a>(doc: &automerge::Automerge) -> RealizedObject<'a> {
 | 
			
		||||
    realize_obj(doc, ObjId::Root, automerge::ObjType::Map)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn realize_prop<P: Into<automerge::Prop>>(
 | 
			
		||||
    doc: &automerge::Automerge,
 | 
			
		||||
    obj_id: automerge::OpId,
 | 
			
		||||
    obj_id: automerge::ObjId,
 | 
			
		||||
    prop: P,
 | 
			
		||||
) -> RealizedObject<ExportedOpId> {
 | 
			
		||||
) -> RealizedObject<'static> {
 | 
			
		||||
    let (val, obj_id) = doc.value(obj_id, prop).unwrap().unwrap();
 | 
			
		||||
    match val {
 | 
			
		||||
        automerge::Value::Object(obj_type) => realize_obj(doc, obj_id, obj_type),
 | 
			
		||||
        automerge::Value::Object(obj_type) => realize_obj(doc, obj_id.into(), obj_type),
 | 
			
		||||
        automerge::Value::Scalar(v) => RealizedObject::Value(v),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn realize_obj(
 | 
			
		||||
    doc: &automerge::Automerge,
 | 
			
		||||
    obj_id: automerge::OpId,
 | 
			
		||||
    obj_id: automerge::ObjId,
 | 
			
		||||
    objtype: automerge::ObjType,
 | 
			
		||||
) -> RealizedObject<ExportedOpId> {
 | 
			
		||||
) -> RealizedObject<'static> {
 | 
			
		||||
    match objtype {
 | 
			
		||||
        automerge::ObjType::Map | automerge::ObjType::Table => {
 | 
			
		||||
            let mut result = HashMap::new();
 | 
			
		||||
            for key in doc.keys(obj_id) {
 | 
			
		||||
                result.insert(key.clone(), realize_values(doc, obj_id, key));
 | 
			
		||||
            for key in doc.keys(obj_id.clone()) {
 | 
			
		||||
                result.insert(key.clone(), realize_values(doc, obj_id.clone(), key));
 | 
			
		||||
            }
 | 
			
		||||
            RealizedObject::Map(result)
 | 
			
		||||
        }
 | 
			
		||||
        automerge::ObjType::List | automerge::ObjType::Text => {
 | 
			
		||||
            let length = doc.length(obj_id);
 | 
			
		||||
            let length = doc.length(obj_id.clone());
 | 
			
		||||
            let mut result = Vec::with_capacity(length);
 | 
			
		||||
            for i in 0..length {
 | 
			
		||||
                result.push(realize_values(doc, obj_id, i));
 | 
			
		||||
                result.push(realize_values(doc, obj_id.clone(), i));
 | 
			
		||||
            }
 | 
			
		||||
            RealizedObject::Sequence(result)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -334,55 +301,25 @@ pub fn realize_obj(
 | 
			
		|||
 | 
			
		||||
fn realize_values<K: Into<automerge::Prop>>(
 | 
			
		||||
    doc: &automerge::Automerge,
 | 
			
		||||
    obj_id: automerge::OpId,
 | 
			
		||||
    obj_id: automerge::ObjId,
 | 
			
		||||
    key: K,
 | 
			
		||||
) -> HashMap<ExportedOpId, RealizedObject<ExportedOpId>> {
 | 
			
		||||
    let mut values_by_opid = HashMap::new();
 | 
			
		||||
) -> HashMap<ObjId<'static>, RealizedObject<'static>> {
 | 
			
		||||
    let mut values_by_objid: HashMap<ObjId, RealizedObject> = HashMap::new();
 | 
			
		||||
    for (value, opid) in doc.values(obj_id, key).unwrap() {
 | 
			
		||||
        let realized = match value {
 | 
			
		||||
            automerge::Value::Object(objtype) => realize_obj(doc, opid, objtype),
 | 
			
		||||
            automerge::Value::Object(objtype) => realize_obj(doc, opid.clone().into(), objtype),
 | 
			
		||||
            automerge::Value::Scalar(v) => RealizedObject::Value(v),
 | 
			
		||||
        };
 | 
			
		||||
        let exported_opid = ExportedOpId(doc.export(opid));
 | 
			
		||||
        values_by_opid.insert(exported_opid, realized);
 | 
			
		||||
        values_by_objid.insert(opid.into(), realized);
 | 
			
		||||
    }
 | 
			
		||||
    values_by_opid
 | 
			
		||||
    values_by_objid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> RealizedObject<ExportableOpId<'a>> {
 | 
			
		||||
    pub fn export(self, doc: &automerge::Automerge) -> RealizedObject<ExportedOpId> {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Map(kvs) => RealizedObject::Map(
 | 
			
		||||
                kvs.into_iter()
 | 
			
		||||
                    .map(|(k, v)| {
 | 
			
		||||
                        (
 | 
			
		||||
                            k,
 | 
			
		||||
                            v.into_iter()
 | 
			
		||||
                                .map(|(k, v)| (k.export(doc), v.export(doc)))
 | 
			
		||||
                                .collect(),
 | 
			
		||||
                        )
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect(),
 | 
			
		||||
            ),
 | 
			
		||||
            Self::Sequence(values) => RealizedObject::Sequence(
 | 
			
		||||
                values
 | 
			
		||||
                    .into_iter()
 | 
			
		||||
                    .map(|v| {
 | 
			
		||||
                        v.into_iter()
 | 
			
		||||
                            .map(|(k, v)| (k.export(doc), v.export(doc)))
 | 
			
		||||
                            .collect()
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect(),
 | 
			
		||||
            ),
 | 
			
		||||
            Self::Value(v) => RealizedObject::Value(v),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>>>
 | 
			
		||||
    From<HashMap<&str, HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
 | 
			
		||||
impl<'a, I: Into<RealizedObject<'a>>>
 | 
			
		||||
    From<HashMap<&str, HashMap<ObjId<'a>, I>>> for RealizedObject<'a>
 | 
			
		||||
{
 | 
			
		||||
    fn from(values: HashMap<&str, HashMap<O, I>>) -> Self {
 | 
			
		||||
    fn from(values: HashMap<&str, HashMap<ObjId<'a>, I>>) -> Self {
 | 
			
		||||
        let intoed = values
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|(k, v)| {
 | 
			
		||||
| 
						 | 
				
			
			@ -396,107 +333,44 @@ impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a, O: Into<ExportableOpId<'a>>, I: Into<RealizedObject<ExportableOpId<'a>>>>
 | 
			
		||||
    From<Vec<HashMap<O, I>>> for RealizedObject<ExportableOpId<'a>>
 | 
			
		||||
impl<'a, I: Into<RealizedObject<'a>>>
 | 
			
		||||
    From<Vec<HashMap<ObjId<'a>, I>>> for RealizedObject<'a>
 | 
			
		||||
{
 | 
			
		||||
    fn from(values: Vec<HashMap<O, I>>) -> Self {
 | 
			
		||||
    fn from(values: Vec<HashMap<ObjId<'a>, I>>) -> Self {
 | 
			
		||||
        RealizedObject::Sequence(
 | 
			
		||||
            values
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .map(|v| v.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
 | 
			
		||||
                .map(|v| v.into_iter().map(|(k, v)| (k, v.into())).collect())
 | 
			
		||||
                .collect(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<bool> for RealizedObject<ExportableOpId<'_>> {
 | 
			
		||||
impl From<bool> for RealizedObject<'static> {
 | 
			
		||||
    fn from(b: bool) -> Self {
 | 
			
		||||
        RealizedObject::Value(b.into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<usize> for RealizedObject<ExportableOpId<'_>> {
 | 
			
		||||
impl From<usize> for RealizedObject<'static> {
 | 
			
		||||
    fn from(u: usize) -> Self {
 | 
			
		||||
        let v = u.try_into().unwrap();
 | 
			
		||||
        RealizedObject::Value(automerge::ScalarValue::Int(v))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<automerge::ScalarValue> for RealizedObject<ExportableOpId<'_>> {
 | 
			
		||||
impl From<automerge::ScalarValue> for RealizedObject<'static> {
 | 
			
		||||
    fn from(s: automerge::ScalarValue) -> Self {
 | 
			
		||||
        RealizedObject::Value(s)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&str> for RealizedObject<ExportableOpId<'_>> {
 | 
			
		||||
impl From<&str> for RealizedObject<'static> {
 | 
			
		||||
    fn from(s: &str) -> Self {
 | 
			
		||||
        RealizedObject::Value(automerge::ScalarValue::Str(s.into()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Eq, PartialEq, Hash)]
 | 
			
		||||
pub enum ExportableOpId<'a> {
 | 
			
		||||
    Native(automerge::OpId),
 | 
			
		||||
    Translate(Translate<'a>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> ExportableOpId<'a> {
 | 
			
		||||
    fn export(self, doc: &automerge::Automerge) -> ExportedOpId {
 | 
			
		||||
        let oid = match self {
 | 
			
		||||
            Self::Native(oid) => oid,
 | 
			
		||||
            Self::Translate(Translate { from, opid }) => translate_obj_id(from, doc, opid),
 | 
			
		||||
        };
 | 
			
		||||
        ExportedOpId(doc.export(oid))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Translate<'a> {
 | 
			
		||||
    from: &'a automerge::Automerge,
 | 
			
		||||
    opid: automerge::OpId,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> PartialEq for Translate<'a> {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.from.maybe_get_actor().unwrap() == other.from.maybe_get_actor().unwrap()
 | 
			
		||||
            && self.opid == other.opid
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Eq for Translate<'a> {}
 | 
			
		||||
 | 
			
		||||
impl<'a> Hash for Translate<'a> {
 | 
			
		||||
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
 | 
			
		||||
        self.from.maybe_get_actor().unwrap().hash(state);
 | 
			
		||||
        self.opid.hash(state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait OpIdExt {
 | 
			
		||||
    fn native(self) -> ExportableOpId<'static>;
 | 
			
		||||
    fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl OpIdExt for automerge::OpId {
 | 
			
		||||
    /// Use this opid directly when exporting
 | 
			
		||||
    fn native(self) -> ExportableOpId<'static> {
 | 
			
		||||
        ExportableOpId::Native(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Translate this OpID from `doc` when exporting
 | 
			
		||||
    fn translate(self, doc: &automerge::Automerge) -> ExportableOpId<'_> {
 | 
			
		||||
        ExportableOpId::Translate(Translate {
 | 
			
		||||
            from: doc,
 | 
			
		||||
            opid: self,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<automerge::OpId> for ExportableOpId<'_> {
 | 
			
		||||
    fn from(oid: automerge::OpId) -> Self {
 | 
			
		||||
        ExportableOpId::Native(oid)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Pretty print the contents of a document
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub fn pretty_print(doc: &automerge::Automerge) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,16 @@
 | 
			
		|||
use automerge::Automerge;
 | 
			
		||||
use automerge::{Automerge, ObjId};
 | 
			
		||||
 | 
			
		||||
mod helpers;
 | 
			
		||||
#[allow(unused_imports)]
 | 
			
		||||
use helpers::{
 | 
			
		||||
    mk_counter, new_doc, new_doc_with_actor, pretty_print, realize, realize_obj, sorted_actors,
 | 
			
		||||
    translate_obj_id, OpIdExt, RealizedObject,
 | 
			
		||||
    RealizedObject,
 | 
			
		||||
};
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_conflict_on_repeated_assignment() {
 | 
			
		||||
    let mut doc = Automerge::new();
 | 
			
		||||
    doc.set(automerge::ROOT, "foo", 1).unwrap();
 | 
			
		||||
    let op = doc.set(automerge::ROOT, "foo", 2).unwrap().unwrap();
 | 
			
		||||
    doc.set(ObjId::Root, "foo", 1).unwrap();
 | 
			
		||||
    let op = doc.set(ObjId::Root, "foo", 2).unwrap().unwrap();
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc,
 | 
			
		||||
        map! {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,19 +22,19 @@ fn no_conflict_on_repeated_assignment() {
 | 
			
		|||
#[test]
 | 
			
		||||
fn no_change_on_repeated_map_set() {
 | 
			
		||||
    let mut doc = new_doc();
 | 
			
		||||
    doc.set(automerge::ROOT, "foo", 1).unwrap();
 | 
			
		||||
    assert!(doc.set(automerge::ROOT, "foo", 1).unwrap().is_none());
 | 
			
		||||
    doc.set(ObjId::Root, "foo", 1).unwrap();
 | 
			
		||||
    assert!(doc.set(ObjId::Root, "foo", 1).unwrap().is_none());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn no_change_on_repeated_list_set() {
 | 
			
		||||
    let mut doc = new_doc();
 | 
			
		||||
    let list_id = doc
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc.insert(list_id, 0, 1).unwrap();
 | 
			
		||||
    doc.set(list_id, 0, 1).unwrap();
 | 
			
		||||
        .unwrap().into();
 | 
			
		||||
    doc.insert(&list_id, 0, 1).unwrap();
 | 
			
		||||
    doc.set(&list_id, 0, 1).unwrap();
 | 
			
		||||
    assert!(doc.set(list_id, 0, 1).unwrap().is_none());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,25 +42,25 @@ fn no_change_on_repeated_list_set() {
 | 
			
		|||
fn no_change_on_list_insert_followed_by_set_of_same_value() {
 | 
			
		||||
    let mut doc = new_doc();
 | 
			
		||||
    let list_id = doc
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc.insert(list_id, 0, 1).unwrap();
 | 
			
		||||
    assert!(doc.set(list_id, 0, 1).unwrap().is_none());
 | 
			
		||||
    doc.insert(&list_id, 0, 1).unwrap();
 | 
			
		||||
    assert!(doc.set(&list_id, 0, 1).unwrap().is_none());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn repeated_map_assignment_which_resolves_conflict_not_ignored() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    doc1.set(automerge::ROOT, "field", 123).unwrap();
 | 
			
		||||
    doc1.set(ObjId::Root, "field", 123).unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    doc2.set(automerge::ROOT, "field", 456).unwrap();
 | 
			
		||||
    doc1.set(automerge::ROOT, "field", 789).unwrap();
 | 
			
		||||
    doc2.set(ObjId::Root, "field", 456).unwrap();
 | 
			
		||||
    doc1.set(ObjId::Root, "field", 789).unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    assert_eq!(doc1.values(automerge::ROOT, "field").unwrap().len(), 2);
 | 
			
		||||
    assert_eq!(doc1.values(ObjId::Root, "field").unwrap().len(), 2);
 | 
			
		||||
 | 
			
		||||
    let op = doc1.set(automerge::ROOT, "field", 123).unwrap().unwrap();
 | 
			
		||||
    let op = doc1.set(ObjId::Root, "field", 123).unwrap().unwrap();
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
| 
						 | 
				
			
			@ -76,15 +76,14 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.insert(list_id, 0, 123).unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 0, 123).unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    doc2.set(list_id_in_doc2, 0, 456).unwrap().unwrap();
 | 
			
		||||
    doc2.set(&list_id, 0, 456).unwrap().unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    let doc1_op = doc1.set(list_id, 0, 789).unwrap().unwrap();
 | 
			
		||||
    let doc1_op = doc1.set(&list_id, 0, 789).unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
| 
						 | 
				
			
			@ -102,13 +101,13 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() {
 | 
			
		|||
fn list_deletion() {
 | 
			
		||||
    let mut doc = new_doc();
 | 
			
		||||
    let list_id = doc
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let op1 = doc.insert(list_id, 0, 123).unwrap();
 | 
			
		||||
    doc.insert(list_id, 1, 456).unwrap();
 | 
			
		||||
    let op3 = doc.insert(list_id, 2, 789).unwrap();
 | 
			
		||||
    doc.del(list_id, 1).unwrap();
 | 
			
		||||
    let op1 = doc.insert(&list_id, 0, 123).unwrap();
 | 
			
		||||
    doc.insert(&list_id, 1, 456).unwrap();
 | 
			
		||||
    let op3 = doc.insert(&list_id.clone(), 2, 789).unwrap();
 | 
			
		||||
    doc.del(&list_id, 1).unwrap();
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc,
 | 
			
		||||
        map! {
 | 
			
		||||
| 
						 | 
				
			
			@ -124,28 +123,28 @@ fn list_deletion() {
 | 
			
		|||
fn merge_concurrent_map_prop_updates() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let op1 = doc1.set(automerge::ROOT, "foo", "bar").unwrap().unwrap();
 | 
			
		||||
    let op1 = doc1.set(ObjId::Root, "foo", "bar").unwrap().unwrap();
 | 
			
		||||
    let hello = doc2
 | 
			
		||||
        .set(automerge::ROOT, "hello", "world")
 | 
			
		||||
        .set(ObjId::Root, "hello", "world")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        doc1.value(automerge::ROOT, "foo").unwrap().unwrap().0,
 | 
			
		||||
        doc1.value(ObjId::Root, "foo").unwrap().unwrap().0,
 | 
			
		||||
        "bar".into()
 | 
			
		||||
    );
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "foo" => { op1 => "bar" },
 | 
			
		||||
            "hello" => { hello.translate(&doc2) => "world" },
 | 
			
		||||
            "hello" => { hello => "world" },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "foo" => { op1.translate(&doc1) => "bar" },
 | 
			
		||||
            "foo" => { op1 => "bar" },
 | 
			
		||||
            "hello" => { hello => "world" },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -157,12 +156,12 @@ fn add_concurrent_increments_of_same_property() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let counter_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "counter", mk_counter(0))
 | 
			
		||||
        .set(ObjId::Root, "counter", mk_counter(0))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    doc1.inc(automerge::ROOT, "counter", 1).unwrap();
 | 
			
		||||
    doc2.inc(automerge::ROOT, "counter", 2).unwrap();
 | 
			
		||||
    doc1.inc(ObjId::Root, "counter", 1).unwrap();
 | 
			
		||||
    doc2.inc(ObjId::Root, "counter", 2).unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
| 
						 | 
				
			
			@ -181,17 +180,17 @@ fn add_increments_only_to_preceeded_values() {
 | 
			
		|||
 | 
			
		||||
    // create a counter in doc1
 | 
			
		||||
    let doc1_counter_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "counter", mk_counter(0))
 | 
			
		||||
        .set(ObjId::Root, "counter", mk_counter(0))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.inc(automerge::ROOT, "counter", 1).unwrap();
 | 
			
		||||
    doc1.inc(ObjId::Root, "counter", 1).unwrap();
 | 
			
		||||
 | 
			
		||||
    // create a counter in doc2
 | 
			
		||||
    let doc2_counter_id = doc2
 | 
			
		||||
        .set(automerge::ROOT, "counter", mk_counter(0))
 | 
			
		||||
        .set(ObjId::Root, "counter", mk_counter(0))
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc2.inc(automerge::ROOT, "counter", 3).unwrap();
 | 
			
		||||
    doc2.inc(ObjId::Root, "counter", 3).unwrap();
 | 
			
		||||
 | 
			
		||||
    // The two values should be conflicting rather than added
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
| 
						 | 
				
			
			@ -200,8 +199,8 @@ fn add_increments_only_to_preceeded_values() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "counter" => {
 | 
			
		||||
                doc1_counter_id.native() => mk_counter(1),
 | 
			
		||||
                doc2_counter_id.translate(&doc2) => mk_counter(3),
 | 
			
		||||
                doc1_counter_id => mk_counter(1),
 | 
			
		||||
                doc2_counter_id => mk_counter(3),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -211,8 +210,8 @@ fn add_increments_only_to_preceeded_values() {
 | 
			
		|||
fn concurrent_updates_of_same_field() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let set_one_opid = doc1.set(automerge::ROOT, "field", "one").unwrap().unwrap();
 | 
			
		||||
    let set_two_opid = doc2.set(automerge::ROOT, "field", "two").unwrap().unwrap();
 | 
			
		||||
    let set_one_opid = doc1.set(ObjId::Root, "field", "one").unwrap().unwrap();
 | 
			
		||||
    let set_two_opid = doc2.set(ObjId::Root, "field", "two").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -220,8 +219,8 @@ fn concurrent_updates_of_same_field() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "field" => {
 | 
			
		||||
                set_one_opid.native() => "one",
 | 
			
		||||
                set_two_opid.translate(&doc2) => "two",
 | 
			
		||||
                set_one_opid => "one",
 | 
			
		||||
                set_two_opid => "two",
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -232,14 +231,13 @@ fn concurrent_updates_of_same_list_element() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.insert(list_id, 0, "finch").unwrap();
 | 
			
		||||
    doc1.insert(list_id.clone(), 0, "finch").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let set_one_op = doc1.set(list_id, 0, "greenfinch").unwrap().unwrap();
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    let set_op_two = doc2.set(list_id_in_doc2, 0, "goldfinch").unwrap().unwrap();
 | 
			
		||||
    let set_one_op = doc1.set(&list_id, 0, "greenfinch").unwrap().unwrap();
 | 
			
		||||
    let set_op_two = doc2.set(&list_id, 0, "goldfinch").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -248,8 +246,8 @@ fn concurrent_updates_of_same_list_element() {
 | 
			
		|||
        map! {
 | 
			
		||||
            "birds" => {
 | 
			
		||||
                list_id => list![{
 | 
			
		||||
                    set_one_op.native() => "greenfinch",
 | 
			
		||||
                    set_op_two.translate(&doc2) => "goldfinch",
 | 
			
		||||
                    set_one_op => "greenfinch",
 | 
			
		||||
                    set_op_two => "goldfinch",
 | 
			
		||||
                }]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -262,15 +260,15 @@ fn assignment_conflicts_of_different_types() {
 | 
			
		|||
    let mut doc2 = new_doc();
 | 
			
		||||
    let mut doc3 = new_doc();
 | 
			
		||||
    let op_one = doc1
 | 
			
		||||
        .set(automerge::ROOT, "field", "string")
 | 
			
		||||
        .set(ObjId::Root, "field", "string")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let op_two = doc2
 | 
			
		||||
        .set(automerge::ROOT, "field", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "field", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let op_three = doc3
 | 
			
		||||
        .set(automerge::ROOT, "field", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "field", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -281,9 +279,9 @@ fn assignment_conflicts_of_different_types() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "field" => {
 | 
			
		||||
                op_one.native() => "string",
 | 
			
		||||
                op_two.translate(&doc2) => list!{},
 | 
			
		||||
                op_three.translate(&doc3) => map!{},
 | 
			
		||||
                op_one => "string",
 | 
			
		||||
                op_two => list!{},
 | 
			
		||||
                op_three => map!{},
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -294,24 +292,24 @@ fn changes_within_conflicting_map_field() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let op_one = doc1
 | 
			
		||||
        .set(automerge::ROOT, "field", "string")
 | 
			
		||||
        .set(ObjId::Root, "field", "string")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let map_id = doc2
 | 
			
		||||
        .set(automerge::ROOT, "field", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "field", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let set_in_doc2 = doc2.set(map_id, "innerKey", 42).unwrap().unwrap();
 | 
			
		||||
    let set_in_doc2 = doc2.set(&map_id, "innerKey", 42).unwrap().unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "field" => {
 | 
			
		||||
                op_one.native() => "string",
 | 
			
		||||
                map_id.translate(&doc2) => map!{
 | 
			
		||||
                op_one => "string",
 | 
			
		||||
                map_id => map!{
 | 
			
		||||
                    "innerKey" => {
 | 
			
		||||
                        set_in_doc2.translate(&doc2) => 42,
 | 
			
		||||
                        set_in_doc2 => 42,
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -325,27 +323,26 @@ fn changes_within_conflicting_list_element() {
 | 
			
		|||
    let mut doc1 = new_doc_with_actor(actor1);
 | 
			
		||||
    let mut doc2 = new_doc_with_actor(actor2);
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.insert(list_id, 0, "hello").unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 0, "hello").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    let map_in_doc1 = doc1
 | 
			
		||||
        .set(list_id, 0, automerge::Value::map())
 | 
			
		||||
        .set(&list_id, 0, automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let set_map1 = doc1.set(map_in_doc1, "map1", true).unwrap().unwrap();
 | 
			
		||||
    let set_key1 = doc1.set(map_in_doc1, "key", 1).unwrap().unwrap();
 | 
			
		||||
    let set_map1 = doc1.set(&map_in_doc1, "map1", true).unwrap().unwrap();
 | 
			
		||||
    let set_key1 = doc1.set(&map_in_doc1, "key", 1).unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    let map_in_doc2 = doc2
 | 
			
		||||
        .set(list_id_in_doc2, 0, automerge::Value::map())
 | 
			
		||||
        .set(&list_id, 0, automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    let set_map2 = doc2.set(map_in_doc2, "map2", true).unwrap().unwrap();
 | 
			
		||||
    let set_key2 = doc2.set(map_in_doc2, "key", 2).unwrap().unwrap();
 | 
			
		||||
    let set_map2 = doc2.set(&map_in_doc2, "map2", true).unwrap().unwrap();
 | 
			
		||||
    let set_key2 = doc2.set(&map_in_doc2, "key", 2).unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -355,13 +352,13 @@ fn changes_within_conflicting_list_element() {
 | 
			
		|||
            "list" => {
 | 
			
		||||
                list_id => list![
 | 
			
		||||
                    {
 | 
			
		||||
                        map_in_doc2.translate(&doc2) => map!{
 | 
			
		||||
                            "map2" => { set_map2.translate(&doc2) => true },
 | 
			
		||||
                            "key" => { set_key2.translate(&doc2) => 2 },
 | 
			
		||||
                        map_in_doc2 => map!{
 | 
			
		||||
                            "map2" => { set_map2 => true },
 | 
			
		||||
                            "key" => { set_key2 => 2 },
 | 
			
		||||
                        },
 | 
			
		||||
                        map_in_doc1.native() => map!{
 | 
			
		||||
                            "key" => { set_key1.native() => 1 },
 | 
			
		||||
                            "map1" => { set_map1.native() => true },
 | 
			
		||||
                        map_in_doc1 => map!{
 | 
			
		||||
                            "key" => { set_key1 => 1 },
 | 
			
		||||
                            "map1" => { set_map1 => true },
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
| 
						 | 
				
			
			@ -376,20 +373,20 @@ fn concurrently_assigned_nested_maps_should_not_merge() {
 | 
			
		|||
    let mut doc2 = new_doc();
 | 
			
		||||
 | 
			
		||||
    let doc1_map_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "config", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "config", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let doc1_field = doc1
 | 
			
		||||
        .set(doc1_map_id, "background", "blue")
 | 
			
		||||
        .set(doc1_map_id.clone(), "background", "blue")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let doc2_map_id = doc2
 | 
			
		||||
        .set(automerge::ROOT, "config", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "config", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let doc2_field = doc2
 | 
			
		||||
        .set(doc2_map_id, "logo_url", "logo.png")
 | 
			
		||||
        .set(doc2_map_id.clone(), "logo_url", "logo.png")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -399,11 +396,11 @@ fn concurrently_assigned_nested_maps_should_not_merge() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "config" => {
 | 
			
		||||
                doc1_map_id.native() => map!{
 | 
			
		||||
                    "background" => {doc1_field.native() => "blue"}
 | 
			
		||||
                doc1_map_id => map!{
 | 
			
		||||
                    "background" => {doc1_field => "blue"}
 | 
			
		||||
                },
 | 
			
		||||
                doc2_map_id.translate(&doc2) => map!{
 | 
			
		||||
                    "logo_url" => {doc2_field.translate(&doc2) => "logo.png"}
 | 
			
		||||
                doc2_map_id => map!{
 | 
			
		||||
                    "logo_url" => {doc2_field => "logo.png"}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -418,16 +415,15 @@ fn concurrent_insertions_at_different_list_positions() {
 | 
			
		|||
    assert!(doc1.maybe_get_actor().unwrap() < doc2.maybe_get_actor().unwrap());
 | 
			
		||||
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let one = doc1.insert(list_id, 0, "one").unwrap();
 | 
			
		||||
    let three = doc1.insert(list_id, 1, "three").unwrap();
 | 
			
		||||
    let one = doc1.insert(&list_id, 0, "one").unwrap();
 | 
			
		||||
    let three = doc1.insert(&list_id, 1, "three").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let two = doc1.splice(list_id, 1, 0, vec!["two".into()]).unwrap()[0];
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    let four = doc2.insert(list_id_in_doc2, 2, "four").unwrap();
 | 
			
		||||
    let two = doc1.splice(&list_id, 1, 0, vec!["two".into()]).unwrap()[0].clone();
 | 
			
		||||
    let four = doc2.insert(&list_id, 2, "four").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -436,10 +432,10 @@ fn concurrent_insertions_at_different_list_positions() {
 | 
			
		|||
        map! {
 | 
			
		||||
            "list" => {
 | 
			
		||||
                list_id => list![
 | 
			
		||||
                    {one.native() => "one"},
 | 
			
		||||
                    {two.native() => "two"},
 | 
			
		||||
                    {three.native() => "three"},
 | 
			
		||||
                    {four.translate(&doc2) => "four"},
 | 
			
		||||
                    {one => "one"},
 | 
			
		||||
                    {two => "two"},
 | 
			
		||||
                    {three => "three"},
 | 
			
		||||
                    {four => "four"},
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -454,15 +450,14 @@ fn concurrent_insertions_at_same_list_position() {
 | 
			
		|||
    assert!(doc1.maybe_get_actor().unwrap() < doc2.maybe_get_actor().unwrap());
 | 
			
		||||
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let parakeet = doc1.insert(list_id, 0, "parakeet").unwrap();
 | 
			
		||||
    let parakeet = doc1.insert(&list_id, 0, "parakeet").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    let starling = doc1.insert(list_id, 1, "starling").unwrap();
 | 
			
		||||
    let chaffinch = doc2.insert(list_id_in_doc2, 1, "chaffinch").unwrap();
 | 
			
		||||
    let starling = doc1.insert(&list_id, 1, "starling").unwrap();
 | 
			
		||||
    let chaffinch = doc2.insert(&list_id, 1, "chaffinch").unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
| 
						 | 
				
			
			@ -471,13 +466,13 @@ fn concurrent_insertions_at_same_list_position() {
 | 
			
		|||
            "birds" => {
 | 
			
		||||
                list_id => list![
 | 
			
		||||
                    {
 | 
			
		||||
                        parakeet.native() => "parakeet",
 | 
			
		||||
                        parakeet => "parakeet",
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        starling.native() => "starling",
 | 
			
		||||
                        starling => "starling",
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        chaffinch.translate(&doc2) => "chaffinch",
 | 
			
		||||
                        chaffinch => "chaffinch",
 | 
			
		||||
                    },
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
| 
						 | 
				
			
			@ -489,11 +484,11 @@ fn concurrent_insertions_at_same_list_position() {
 | 
			
		|||
fn concurrent_assignment_and_deletion_of_a_map_entry() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    doc1.set(automerge::ROOT, "bestBird", "robin").unwrap();
 | 
			
		||||
    doc1.set(ObjId::Root, "bestBird", "robin").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    doc1.del(automerge::ROOT, "bestBird").unwrap();
 | 
			
		||||
    doc1.del(ObjId::Root, "bestBird").unwrap();
 | 
			
		||||
    let set_two = doc2
 | 
			
		||||
        .set(automerge::ROOT, "bestBird", "magpie")
 | 
			
		||||
        .set(ObjId::Root, "bestBird", "magpie")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +498,7 @@ fn concurrent_assignment_and_deletion_of_a_map_entry() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "bestBird" => {
 | 
			
		||||
                set_two.translate(&doc2) => "magpie",
 | 
			
		||||
                set_two => "magpie",
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -514,25 +509,24 @@ fn concurrent_assignment_and_deletion_of_list_entry() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let blackbird = doc1.insert(list_id, 0, "blackbird").unwrap();
 | 
			
		||||
    doc1.insert(list_id, 1, "thrush").unwrap();
 | 
			
		||||
    let goldfinch = doc1.insert(list_id, 2, "goldfinch").unwrap();
 | 
			
		||||
    let blackbird = doc1.insert(&list_id, 0, "blackbird").unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 1, "thrush").unwrap();
 | 
			
		||||
    let goldfinch = doc1.insert(&list_id, 2, "goldfinch").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    let starling = doc1.set(list_id, 1, "starling").unwrap().unwrap();
 | 
			
		||||
    let starling = doc1.set(&list_id, 1, "starling").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    doc2.del(list_id_in_doc2, 1).unwrap();
 | 
			
		||||
    doc2.del(&list_id, 1).unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id.translate(&doc1) => list![
 | 
			
		||||
                { blackbird.translate(&doc1) => "blackbird"},
 | 
			
		||||
                { goldfinch.translate(&doc1) => "goldfinch"},
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
                { blackbird => "blackbird"},
 | 
			
		||||
                { goldfinch => "goldfinch"},
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -540,9 +534,9 @@ fn concurrent_assignment_and_deletion_of_list_entry() {
 | 
			
		|||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
            "birds" => {list_id.clone() => list![
 | 
			
		||||
                { blackbird => "blackbird" },
 | 
			
		||||
                { starling => "starling" },
 | 
			
		||||
                { starling.clone() => "starling" },
 | 
			
		||||
                { goldfinch => "goldfinch" },
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -567,22 +561,21 @@ fn insertion_after_a_deleted_list_element() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let blackbird = doc1.insert(list_id, 0, "blackbird").unwrap();
 | 
			
		||||
    doc1.insert(list_id, 1, "thrush").unwrap();
 | 
			
		||||
    doc1.insert(list_id, 2, "goldfinch").unwrap();
 | 
			
		||||
    let blackbird = doc1.insert(list_id.clone(), 0, "blackbird").unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 1, "thrush").unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 2, "goldfinch").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    doc1.splice(list_id, 1, 2, Vec::new()).unwrap();
 | 
			
		||||
    doc1.splice(&list_id, 1, 2, Vec::new()).unwrap();
 | 
			
		||||
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    let starling = doc2
 | 
			
		||||
        .splice(list_id_in_doc2, 2, 0, vec!["starling".into()])
 | 
			
		||||
        .unwrap()[0];
 | 
			
		||||
        .splice(&list_id, 2, 0, vec!["starling".into()])
 | 
			
		||||
        .unwrap()[0].clone();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -590,8 +583,8 @@ fn insertion_after_a_deleted_list_element() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
                { blackbird.native() => "blackbird" },
 | 
			
		||||
                { starling.translate(&doc2) => "starling" }
 | 
			
		||||
                { blackbird => "blackbird" },
 | 
			
		||||
                { starling => "starling" }
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -600,9 +593,9 @@ fn insertion_after_a_deleted_list_element() {
 | 
			
		|||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id.translate(&doc1) => list![
 | 
			
		||||
                { blackbird.translate(&doc1) => "blackbird" },
 | 
			
		||||
                { starling.native() => "starling" }
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
                { blackbird => "blackbird" },
 | 
			
		||||
                { starling => "starling" }
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -613,29 +606,28 @@ fn concurrent_deletion_of_same_list_element() {
 | 
			
		|||
    let mut doc1 = new_doc();
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    let list_id = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let albatross = doc1.insert(list_id, 0, "albatross").unwrap();
 | 
			
		||||
    doc1.insert(list_id, 1, "buzzard").unwrap();
 | 
			
		||||
    let cormorant = doc1.insert(list_id, 2, "cormorant").unwrap();
 | 
			
		||||
    let albatross = doc1.insert(list_id.clone(), 0, "albatross").unwrap();
 | 
			
		||||
    doc1.insert(&list_id, 1, "buzzard").unwrap();
 | 
			
		||||
    let cormorant = doc1.insert(&list_id, 2, "cormorant").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    doc1.del(list_id, 1).unwrap();
 | 
			
		||||
    doc1.del(&list_id, 1).unwrap();
 | 
			
		||||
 | 
			
		||||
    let list_id_in_doc2 = translate_obj_id(&doc1, &doc2, list_id);
 | 
			
		||||
    doc2.del(list_id_in_doc2, 1).unwrap();
 | 
			
		||||
    doc2.del(&list_id, 1).unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
                { albatross => "albatross" },
 | 
			
		||||
                { cormorant  => "cormorant" }
 | 
			
		||||
            "birds" => {list_id.clone() => list![
 | 
			
		||||
                { albatross.clone() => "albatross" },
 | 
			
		||||
                { cormorant.clone()  => "cormorant" }
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -644,9 +636,9 @@ fn concurrent_deletion_of_same_list_element() {
 | 
			
		|||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "birds" => {list_id.translate(&doc1) => list![
 | 
			
		||||
                { albatross.translate(&doc1) => "albatross" },
 | 
			
		||||
                { cormorant.translate(&doc1) => "cormorant" }
 | 
			
		||||
            "birds" => {list_id => list![
 | 
			
		||||
                { albatross => "albatross" },
 | 
			
		||||
                { cormorant => "cormorant" }
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -658,33 +650,32 @@ fn concurrent_updates_at_different_levels() {
 | 
			
		|||
    let mut doc2 = new_doc();
 | 
			
		||||
 | 
			
		||||
    let animals = doc1
 | 
			
		||||
        .set(automerge::ROOT, "animals", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "animals", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let birds = doc1
 | 
			
		||||
        .set(animals, "birds", automerge::Value::map())
 | 
			
		||||
        .set(&animals, "birds", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.set(birds, "pink", "flamingo").unwrap().unwrap();
 | 
			
		||||
    doc1.set(birds, "black", "starling").unwrap().unwrap();
 | 
			
		||||
    doc1.set(&birds, "pink", "flamingo").unwrap().unwrap();
 | 
			
		||||
    doc1.set(&birds, "black", "starling").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    let mammals = doc1
 | 
			
		||||
        .set(animals, "mammals", automerge::Value::list())
 | 
			
		||||
        .set(&animals, "mammals", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let badger = doc1.insert(mammals, 0, "badger").unwrap();
 | 
			
		||||
    let badger = doc1.insert(&mammals, 0, "badger").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    doc1.set(birds, "brown", "sparrow").unwrap().unwrap();
 | 
			
		||||
    doc1.set(&birds, "brown", "sparrow").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    let animals_in_doc2 = translate_obj_id(&doc1, &doc2, animals);
 | 
			
		||||
    doc2.del(animals_in_doc2, "birds").unwrap();
 | 
			
		||||
    doc2.del(&animals, "birds").unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
    assert_obj!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        automerge::ROOT,
 | 
			
		||||
        ObjId::Root,
 | 
			
		||||
        "animals",
 | 
			
		||||
        map! {
 | 
			
		||||
            "mammals" => {
 | 
			
		||||
| 
						 | 
				
			
			@ -695,11 +686,11 @@ fn concurrent_updates_at_different_levels() {
 | 
			
		|||
 | 
			
		||||
    assert_obj!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        automerge::ROOT,
 | 
			
		||||
        ObjId::Root,
 | 
			
		||||
        "animals",
 | 
			
		||||
        map! {
 | 
			
		||||
            "mammals" => {
 | 
			
		||||
                mammals.translate(&doc1) => list![{ badger.translate(&doc1) => "badger" }],
 | 
			
		||||
                mammals => list![{ badger => "badger" }],
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -711,21 +702,20 @@ fn concurrent_updates_of_concurrently_deleted_objects() {
 | 
			
		|||
    let mut doc2 = new_doc();
 | 
			
		||||
 | 
			
		||||
    let birds = doc1
 | 
			
		||||
        .set(automerge::ROOT, "birds", automerge::Value::map())
 | 
			
		||||
        .set(ObjId::Root, "birds", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let blackbird = doc1
 | 
			
		||||
        .set(birds, "blackbird", automerge::Value::map())
 | 
			
		||||
        .set(&birds, "blackbird", automerge::Value::map())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.set(blackbird, "feathers", "black").unwrap().unwrap();
 | 
			
		||||
    doc1.set(&blackbird, "feathers", "black").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    doc1.del(birds, "blackbird").unwrap();
 | 
			
		||||
    doc1.del(&birds, "blackbird").unwrap();
 | 
			
		||||
 | 
			
		||||
    translate_obj_id(&doc1, &doc2, blackbird);
 | 
			
		||||
    doc2.set(blackbird, "beak", "orange").unwrap();
 | 
			
		||||
    doc2.set(&blackbird, "beak", "orange").unwrap();
 | 
			
		||||
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -746,14 +736,14 @@ fn does_not_interleave_sequence_insertions_at_same_position() {
 | 
			
		|||
    let mut doc2 = new_doc_with_actor(actor2);
 | 
			
		||||
 | 
			
		||||
    let wisdom = doc1
 | 
			
		||||
        .set(automerge::ROOT, "wisdom", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "wisdom", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    let doc1elems = doc1
 | 
			
		||||
        .splice(
 | 
			
		||||
            wisdom,
 | 
			
		||||
            &wisdom,
 | 
			
		||||
            0,
 | 
			
		||||
            0,
 | 
			
		||||
            vec![
 | 
			
		||||
| 
						 | 
				
			
			@ -766,10 +756,9 @@ fn does_not_interleave_sequence_insertions_at_same_position() {
 | 
			
		|||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let wisdom_in_doc2 = translate_obj_id(&doc1, &doc2, wisdom);
 | 
			
		||||
    let doc2elems = doc2
 | 
			
		||||
        .splice(
 | 
			
		||||
            wisdom_in_doc2,
 | 
			
		||||
            &wisdom,
 | 
			
		||||
            0,
 | 
			
		||||
            0,
 | 
			
		||||
            vec![
 | 
			
		||||
| 
						 | 
				
			
			@ -788,16 +777,16 @@ fn does_not_interleave_sequence_insertions_at_same_position() {
 | 
			
		|||
        &doc1,
 | 
			
		||||
        map! {
 | 
			
		||||
            "wisdom" => {wisdom => list![
 | 
			
		||||
                {doc1elems[0].native() => "to"},
 | 
			
		||||
                {doc1elems[1].native() => "be"},
 | 
			
		||||
                {doc1elems[2].native() => "is"},
 | 
			
		||||
                {doc1elems[3].native() => "to"},
 | 
			
		||||
                {doc1elems[4].native() => "do"},
 | 
			
		||||
                {doc2elems[0].translate(&doc2) => "to"},
 | 
			
		||||
                {doc2elems[1].translate(&doc2) => "do"},
 | 
			
		||||
                {doc2elems[2].translate(&doc2) => "is"},
 | 
			
		||||
                {doc2elems[3].translate(&doc2) => "to"},
 | 
			
		||||
                {doc2elems[4].translate(&doc2) => "be"},
 | 
			
		||||
                {doc1elems[0] => "to"},
 | 
			
		||||
                {doc1elems[1] => "be"},
 | 
			
		||||
                {doc1elems[2] => "is"},
 | 
			
		||||
                {doc1elems[3] => "to"},
 | 
			
		||||
                {doc1elems[4] => "do"},
 | 
			
		||||
                {doc2elems[0] => "to"},
 | 
			
		||||
                {doc2elems[1] => "do"},
 | 
			
		||||
                {doc2elems[2] => "is"},
 | 
			
		||||
                {doc2elems[3] => "to"},
 | 
			
		||||
                {doc2elems[4] => "be"},
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -811,20 +800,19 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id(
 | 
			
		|||
    let mut doc2 = new_doc_with_actor(actor2);
 | 
			
		||||
 | 
			
		||||
    let list = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let two = doc1.insert(list, 0, "two").unwrap();
 | 
			
		||||
    let two = doc1.insert(&list, 0, "two").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    let list_in_doc2 = translate_obj_id(&doc1, &doc2, list);
 | 
			
		||||
    let one = doc2.insert(list_in_doc2, 0, "one").unwrap();
 | 
			
		||||
    let one = doc2.insert(&list, 0, "one").unwrap();
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "list" => { list.translate(&doc1) => list![
 | 
			
		||||
                { one.native() => "one" },
 | 
			
		||||
                { two.translate(&doc1) => "two" },
 | 
			
		||||
            "list" => { list => list![
 | 
			
		||||
                { one => "one" },
 | 
			
		||||
                { two => "two" },
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -838,20 +826,19 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id()
 | 
			
		|||
    let mut doc2 = new_doc_with_actor(actor2);
 | 
			
		||||
 | 
			
		||||
    let list = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let two = doc1.insert(list, 0, "two").unwrap();
 | 
			
		||||
    let two = doc1.insert(&list, 0, "two").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
 | 
			
		||||
    let list_in_doc2 = translate_obj_id(&doc1, &doc2, list);
 | 
			
		||||
    let one = doc2.insert(list_in_doc2, 0, "one").unwrap();
 | 
			
		||||
    let one = doc2.insert(&list, 0, "one").unwrap();
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "list" => { list.translate(&doc1) => list![
 | 
			
		||||
                { one.native() => "one" },
 | 
			
		||||
                { two.translate(&doc1) => "two" },
 | 
			
		||||
            "list" => { list => list![
 | 
			
		||||
                { one => "one" },
 | 
			
		||||
                { two => "two" },
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -863,31 +850,68 @@ fn insertion_consistent_with_causality() {
 | 
			
		|||
    let mut doc2 = new_doc();
 | 
			
		||||
 | 
			
		||||
    let list = doc1
 | 
			
		||||
        .set(automerge::ROOT, "list", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "list", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let four = doc1.insert(list, 0, "four").unwrap();
 | 
			
		||||
    let four = doc1.insert(&list, 0, "four").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let list_in_doc2 = translate_obj_id(&doc1, &doc2, list);
 | 
			
		||||
    let three = doc2.insert(list_in_doc2, 0, "three").unwrap();
 | 
			
		||||
    let three = doc2.insert(&list, 0, "three").unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
    let two = doc1.insert(list, 0, "two").unwrap();
 | 
			
		||||
    let two = doc1.insert(&list, 0, "two").unwrap();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let one = doc2.insert(list_in_doc2, 0, "one").unwrap();
 | 
			
		||||
    let one = doc2.insert(&list, 0, "one").unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc2,
 | 
			
		||||
        map! {
 | 
			
		||||
            "list" => {list.translate(&doc1) => list![
 | 
			
		||||
                {one.native() => "one"},
 | 
			
		||||
                {two.translate(&doc1) => "two"},
 | 
			
		||||
                {three.native() => "three" },
 | 
			
		||||
                {four.translate(&doc1) => "four"},
 | 
			
		||||
            "list" => {list => list![
 | 
			
		||||
                {one => "one"},
 | 
			
		||||
                {two => "two"},
 | 
			
		||||
                {three => "three" },
 | 
			
		||||
                {four => "four"},
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn should_handle_arbitrary_depth_nesting() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let a = doc1.set(ObjId::Root, "a", automerge::Value::map()).unwrap().unwrap(); 
 | 
			
		||||
    let b = doc1.set(&a, "b", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let c = doc1.set(&b, "c", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let d = doc1.set(&c, "d", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let e = doc1.set(&d, "e", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let f = doc1.set(&e, "f", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let g = doc1.set(&f, "g", automerge::Value::map()).unwrap().unwrap();
 | 
			
		||||
    let h = doc1.set(&g, "h", "h").unwrap().unwrap();
 | 
			
		||||
    let j = doc1.set(&f, "i", "j").unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_doc!(
 | 
			
		||||
        &doc1,
 | 
			
		||||
        map!{
 | 
			
		||||
            "a" => {a => map!{
 | 
			
		||||
                "b" => {b => map!{
 | 
			
		||||
                    "c" => {c => map!{
 | 
			
		||||
                        "d" => {d => map!{
 | 
			
		||||
                            "e" => {e => map!{
 | 
			
		||||
                                "f" => {f => map!{
 | 
			
		||||
                                    "g" => {g => map!{
 | 
			
		||||
                                        "h" => {h => "h"}
 | 
			
		||||
                                    }},
 | 
			
		||||
                                    "i" => {j => "j"},
 | 
			
		||||
                                }}
 | 
			
		||||
                            }}
 | 
			
		||||
                        }}
 | 
			
		||||
                    }}
 | 
			
		||||
                }}
 | 
			
		||||
            }}
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Automerge::load(&doc1.save().unwrap()).unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn save_and_restore_empty() {
 | 
			
		||||
    let mut doc = new_doc();
 | 
			
		||||
| 
						 | 
				
			
			@ -900,26 +924,25 @@ fn save_and_restore_empty() {
 | 
			
		|||
fn save_restore_complex() {
 | 
			
		||||
    let mut doc1 = new_doc();
 | 
			
		||||
    let todos = doc1
 | 
			
		||||
        .set(automerge::ROOT, "todos", automerge::Value::list())
 | 
			
		||||
        .set(ObjId::Root, "todos", automerge::Value::list())
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let first_todo = doc1.insert(todos, 0, automerge::Value::map()).unwrap();
 | 
			
		||||
    doc1.set(first_todo, "title", "water plants")
 | 
			
		||||
    let first_todo = doc1.insert(todos.clone(), 0, automerge::Value::map()).unwrap();
 | 
			
		||||
    doc1.set(&first_todo, "title", "water plants")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let first_done = doc1.set(first_todo, "done", false).unwrap().unwrap();
 | 
			
		||||
    let first_done = doc1.set(first_todo.clone(), "done", false).unwrap().unwrap();
 | 
			
		||||
 | 
			
		||||
    let mut doc2 = new_doc();
 | 
			
		||||
    doc2.merge(&mut doc1);
 | 
			
		||||
    let first_todo_in_doc2 = translate_obj_id(&doc1, &doc2, first_todo);
 | 
			
		||||
    let weed_title = doc2
 | 
			
		||||
        .set(first_todo_in_doc2, "title", "weed plants")
 | 
			
		||||
        .set(first_todo.clone(), "title", "weed plants")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let kill_title = doc1
 | 
			
		||||
        .set(first_todo, "title", "kill plants")
 | 
			
		||||
        .set(&first_todo, "title", "kill plants")
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    doc1.merge(&mut doc2);
 | 
			
		||||
| 
						 | 
				
			
			@ -929,13 +952,13 @@ fn save_restore_complex() {
 | 
			
		|||
    assert_doc!(
 | 
			
		||||
        &reloaded,
 | 
			
		||||
        map! {
 | 
			
		||||
            "todos" => {todos.translate(&doc1) => list![
 | 
			
		||||
                {first_todo.translate(&doc1) => map!{
 | 
			
		||||
            "todos" => {todos => list![
 | 
			
		||||
                {first_todo => map!{
 | 
			
		||||
                    "title" => {
 | 
			
		||||
                        weed_title.translate(&doc2) => "weed plants",
 | 
			
		||||
                        kill_title.translate(&doc1) => "kill plants",
 | 
			
		||||
                        weed_title => "weed plants",
 | 
			
		||||
                        kill_title => "kill plants",
 | 
			
		||||
                    },
 | 
			
		||||
                    "done" => {first_done.translate(&doc1) => false},
 | 
			
		||||
                    "done" => {first_done => false},
 | 
			
		||||
                }}
 | 
			
		||||
            ]}
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,9 @@ edition = "2018"
 | 
			
		|||
license = "MIT"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "edit-trace"
 | 
			
		||||
bench = false
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
automerge = { path = "../automerge" }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,13 @@
 | 
			
		|||
use automerge::{Automerge, Value, ROOT};
 | 
			
		||||
use automerge::{Automerge, Value, ObjId};
 | 
			
		||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
 | 
			
		||||
use std::fs;
 | 
			
		||||
 | 
			
		||||
fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> Automerge {
 | 
			
		||||
    let mut doc = Automerge::new();
 | 
			
		||||
 | 
			
		||||
    let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
 | 
			
		||||
    let text = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap();
 | 
			
		||||
    for (pos, del, vals) in commands {
 | 
			
		||||
        doc.splice(text, pos, del, vals).unwrap();
 | 
			
		||||
        doc.splice(&text, pos, del, vals).unwrap();
 | 
			
		||||
    }
 | 
			
		||||
    doc.commit(None, None);
 | 
			
		||||
    doc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
use automerge::{Automerge, AutomergeError, Value, ROOT};
 | 
			
		||||
use automerge::{Automerge, AutomergeError, Value, ObjId};
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> {
 | 
			
		|||
    let mut doc = Automerge::new();
 | 
			
		||||
 | 
			
		||||
    let now = Instant::now();
 | 
			
		||||
    let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
 | 
			
		||||
    let text = doc.set(ObjId::Root, "text", Value::text()).unwrap().unwrap();
 | 
			
		||||
    for (i, (pos, del, vals)) in commands.into_iter().enumerate() {
 | 
			
		||||
        if i % 1000 == 0 {
 | 
			
		||||
            println!("Processed {} edits in {} ms", i, now.elapsed().as_millis());
 | 
			
		||||
        }
 | 
			
		||||
        doc.splice(text, pos, del, vals)?;
 | 
			
		||||
        doc.splice(&text, pos, del, vals)?;
 | 
			
		||||
    }
 | 
			
		||||
    let _ = doc.save();
 | 
			
		||||
    println!("Done in {} ms", now.elapsed().as_millis());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								scripts/ci/js_tests
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								scripts/ci/js_tests
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
THIS_SCRIPT=$(dirname "$0");
 | 
			
		||||
WASM_PROJECT=$THIS_SCRIPT/../../automerge-wasm;
 | 
			
		||||
JS_PROJECT=$THIS_SCRIPT/../../automerge-wasm/automerge-js;
 | 
			
		||||
 | 
			
		||||
# This will take care of running wasm-pack
 | 
			
		||||
yarn --cwd $WASM_PROJECT build;
 | 
			
		||||
# If the dependencies are already installed we delete automerge-wasm. This makes
 | 
			
		||||
# this script usable for iterative development.
 | 
			
		||||
if [ -d $JS_PROJECT/node_modules/automerge-wasm ]; then
 | 
			
		||||
    rm -rf $JS_PROJECT/node_modules/automerge-wasm
 | 
			
		||||
fi
 | 
			
		||||
# --check-files forces yarn to check if the local dep has changed
 | 
			
		||||
yarn --cwd $JS_PROJECT install --check-files;
 | 
			
		||||
yarn --cwd $JS_PROJECT test;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6,3 +6,4 @@ set -eou pipefail
 | 
			
		|||
./scripts/ci/build-test
 | 
			
		||||
./scripts/ci/docs
 | 
			
		||||
./scripts/ci/advisory
 | 
			
		||||
./scripts/ci/js_tests
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								todo.adoc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								todo.adoc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
* Add an `ElementId::Head` 
 | 
			
		||||
* Make all fields of Transaction private
 | 
			
		||||
* Remove panics from From implementations in value.rs
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue