Coder Social home page Coder Social logo

autosurgeon's Introduction

Automerge

Automerge logo

homepage main docs latest docs ci docs

Automerge is a library which provides fast implementations of several different CRDTs, a compact compression format for these CRDTs, and a sync protocol for efficiently transmitting those changes over the network. The objective of the project is to support local-first applications in the same way that relational databases support server applications - by providing mechanisms for persistence which allow application developers to avoid thinking about hard distributed computing problems. Automerge aims to be PostgreSQL for your local-first app.

If you're looking for documentation on the JavaScript implementation take a look at https://automerge.org/docs/hello/. There are other implementations in both Rust and C, but they are earlier and don't have documentation yet. You can find them in rust/automerge and rust/automerge-c if you are comfortable reading the code and tests to figure out how to use them.

If you're familiar with CRDTs and interested in the design of Automerge in particular take a look at https://automerge.org/automerge-binary-format-spec.

Finally, if you want to talk to us about this project please join our Discord server!

Status

This project is formed of a core Rust implementation which is exposed via FFI in javascript+WASM, C, and soon other languages. Alex (@alexjg) is working full time on maintaining automerge, other members of Ink and Switch are also contributing time and there are several other maintainers. The focus is currently on shipping the new JS package. We expect to be iterating the API and adding new features over the next six months so there will likely be several major version bumps in all packages in that time.

In general we try and respect semver.

JavaScript

A stable release of the javascript package is currently available as @automerge/[email protected] where. pre-release verisions of the 2.0.1 are available as 2.0.1-alpha.n. 2.0.1* packages are also available for Deno at https://deno.land/x/automerge

Rust

The rust codebase is currently oriented around producing a performant backend for the Javascript wrapper and as such the API for Rust code is low level and not well documented. We will be returning to this over the next few months but for now you will need to be comfortable reading the tests and asking questions to figure out how to use it. If you are looking to build rust applications which use automerge you may want to look into autosurgeon

Repository Organisation

  • ./rust - the rust rust implementation and also the Rust components of platform specific wrappers (e.g. automerge-wasm for the WASM API or automerge-c for the C FFI bindings)
  • ./javascript - The javascript library which uses automerge-wasm internally but presents a more idiomatic javascript interface
  • ./scripts - scripts which are useful to maintenance of the repository. This includes the scripts which are run in CI.
  • ./img - static assets for use in .md files

Building

To build this codebase you will need:

  • rust
  • node
  • yarn
  • cmake
  • cmocka

You will also need to install the following with cargo install

  • wasm-bindgen-cli
  • wasm-opt
  • cargo-deny

And ensure you have added the wasm32-unknown-unknown target for rust cross-compilation.

The various subprojects (the rust code, the wrapper projects) have their own build instructions, but to run the tests that will be run in CI you can run ./scripts/ci/run.

For macOS

These instructions worked to build locally on macOS 13.1 (arm64) as of Nov 29th 2022.

# clone the repo
git clone https://github.com/automerge/automerge
cd automerge

# install rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# install homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# install cmake, node, cmocka
brew install cmake node cmocka

# install yarn
npm install --global yarn

# install javascript dependencies
yarn --cwd ./javascript

# install rust dependencies
cargo install wasm-bindgen-cli wasm-opt cargo-deny

# get nightly rust to produce optimized automerge-c builds
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly

# add wasm target in addition to current architecture
rustup target add wasm32-unknown-unknown

# Run ci script
./scripts/ci/run

If your build fails to find cmocka.h you may need to teach it about homebrew's installation location:

export CPATH=/opt/homebrew/include
export LIBRARY_PATH=/opt/homebrew/lib
./scripts/ci/run

Contributing

Please try and split your changes up into relatively independent commits which change one subsystem at a time and add good commit messages which describe what the change is and why you're making it (err on the side of longer commit messages). git blame should give future maintainers a good idea of why something is the way it is.

Releasing

There are four artefacts in this repository which need releasing:

  • The @automerge/automerge NPM package
  • The @automerge/automerge-wasm NPM package
  • The automerge deno crate
  • The automerge rust crate

JS Packages

The NPM and Deno packages are all released automatically by CI tooling whenever the version number in the respective package.json changes. This means that the process for releasing a new JS version is:

  1. Bump the version in the rust/automerge-wasm/package.json (skip this if there are no new changes to the WASM)
  2. Bump the version of @automerge/automerge-wasm we depend on in javascript/package.json
  3. Bump the version in @automerge/automerge also in javascript/package.json

Put all of these bumps in a PR and wait for a clean CI run. Then merge the PR. The CI tooling will pick up a push to main with a new version and publish it to NPM. This does depend on an access token available as NPM_TOKEN in the actions environment, this token is generated with a 30 day expiry date so needs (manually) refreshing every so often.

Rust Package

This is much easier, but less automatic. The steps to release are:

  1. Bump the version in automerge/Cargo.toml
  2. Push a PR and merge once clean
  3. Tag the release as rust/automerge@<version>
  4. Push the tag to the repository
  5. Publish the release with cargo publish

autosurgeon's People

Contributors

alexjg avatar bluebear94 avatar bobby avatar gterzian avatar nokome avatar satvikpendem avatar teohhanhui avatar xlambein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

autosurgeon's Issues

Build docs.rs with all features

The docs mention that an implementation for uuid is provided behind a feature flag. It would be more discoverable if this was reflected as a documented type, and so searchable.

Removing outer elements in nested `Vec` produces unexpected results

I've noticed what I think is a bug in autosurgeon.

In the test that follows, I create a data structure with a Vec of 3 strings, fork it, remove one element in one document and another in the other document, then merge them back and get a document where both elements are removed, as expected:

#[test]
fn remove_vec_of_strings() {
    #[derive(Hydrate, Reconcile)]
    struct Data {
        rows: Vec<String>,
    }

    // Create data with 3 rows
    let mut data1 = Data {
        rows: vec!["hello".to_owned(), "world".to_owned(), "foobar".to_owned()],
    };
    let mut doc1 = AutoCommit::new();
    reconcile(&mut doc1, &data1).unwrap();

    // Fork into another document
    let mut doc2 = doc1.fork();
    let mut data2: Data = hydrate(&doc2).unwrap();

    // Remove row 0 in first document, and row 1 in second
    data1.rows.remove(0);
    data2.rows.remove(1);
    reconcile(&mut doc1, data1).unwrap();
    reconcile(&mut doc2, data2).unwrap();

    // Merge documents
    doc1.merge(&mut doc2).unwrap();
    let data_merged: Data = hydrate(&doc1).unwrap();

    // Both rows 0 and 1 have been removed
    assert_eq!(data_merged.rows.len(), 1);
    assert_eq!(&data_merged.rows[0], "foobar");
}

However, in the next test, I do the exact same, but the document contains a Vec of three Vecs of bytes. Here, the results are different: we get a document with two elements, in which some values are scrambled. Note that I'm aware of the existence of ByteVec, but for this example I'm not using them.

#[test]
fn remove_vec_of_bytes() {
    #[derive(Hydrate, Reconcile)]
    struct Data {
        rows: Vec<Vec<u8>>,
    }

    // Same as above
    let mut data1 = Data {
        rows: vec![b"hello".to_vec(), b"world".to_vec(), b"foobar".to_vec()],
    };
    let mut doc1 = AutoCommit::new();
    reconcile(&mut doc1, &data1).unwrap();

    let mut doc2 = doc1.fork();
    let mut data2: Data = hydrate(&doc2).unwrap();

    data1.rows.remove(0);
    data2.rows.remove(1);

    reconcile(&mut doc1, data1).unwrap();
    reconcile(&mut doc2, data2).unwrap();
    doc1.merge(&mut doc2).unwrap();
    let data_merged: Data = hydrate(&doc1).unwrap();

    // There are two rows, with the following values:
    assert_eq!(data_merged.rows.len(), 2);
    assert_eq!(&data_merged.rows[0], b"world");
    assert_eq!(&data_merged.rows[1], b"ffoobaobar");
    // Instead, I'd expect to have the same results as the previous test
}

I tried doing things manually with automerge, i.e. creating a document with nested lists, forking, removing elements, and merging again, but I don't get any weird results there, which leads me to conclude that autosurgeon's implementation must have an issue somewhere---or perhaps that I misunderstood something.

Same test again, using `automerge` directly:
#[test]
fn remove_vec_of_bytes_automerge() {
    let mut doc1 = AutoCommit::new();
    let rows = doc1
        .put_object(automerge::ROOT, "rows", automerge::ObjType::List)
        .unwrap();

    let row = doc1
        .insert_object(&rows, 0, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"hello".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }
    let row = doc1
        .insert_object(&rows, 1, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"world".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }
    let row = doc1
        .insert_object(&rows, 2, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"foobar".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }

    let mut doc2 = doc1.fork();

    doc1.delete(&rows, 0).unwrap();
    doc2.delete(&rows, 1).unwrap();
    assert_eq!(doc1.length(&rows), 2);
    assert_eq!(doc2.length(&rows), 2);

    doc1.merge(&mut doc2).unwrap();
    assert_eq!(doc1.length(&rows), 1);
    assert_eq!(
        doc1.list_range(doc1.get(&rows, 0).unwrap().unwrap().1, ..)
            .map(|(_, value, _)| value.to_u64().unwrap() as u8)
            .collect::<Vec<_>>(),
        b"foobar"
    );
}

Add support for HashMap & BTreeMap with parseable keys

I'm working on a prototype where I have hashmaps that use a custom type as key:

#[derive(Reconcile, Hydrate)]
pub struct Document {
    stuff: HashMap<CustomKey, String>,
}

where CustomKey cannot implement either AsRef<str> or From<String>, which mean the derives for Reconcile and Hydrate fail.

I'm suggesting adding support for HashMap<K, V> & BTreeMap<K, V> where Hydrate requires K: FromStr (which is fallible), and Reconcile requires K: ToString. I wrote the following module, that I use with autosurgeon(with=...):

(Long code listing)
mod autosurgeon_hashmap_parseable_keys {
    use std::{collections::HashMap, hash::Hash, str::FromStr};

    use automerge::{ObjId, ObjType, Value};
    use autosurgeon::{
        reconcile::{LoadKey, MapReconciler},
        Hydrate, HydrateError, Prop, ReadDoc, Reconcile, Reconciler,
    };

    pub fn reconcile<R, K, V>(items: &HashMap<K, V>, mut reconciler: R) -> Result<(), R::Error>
    where
        R: Reconciler,
        K: ToString,
        V: Reconcile,
    {
        let mut m = reconciler.map()?;
        for (k, val) in items {
            let k = k.to_string();
            if let LoadKey::Found(new_key) = val.key() {
                if let LoadKey::Found(existing_key) = m.hydrate_entry_key::<V, _>(&k)? {
                    if existing_key != new_key {
                        m.replace(k, val)?;
                        continue;
                    }
                }
            }
            m.put(k, val)?;
        }
        Ok(())
    }

    pub fn hydrate<'a, D, K, V>(
        doc: &D,
        obj: &ObjId,
        prop: Prop<'a>,
    ) -> Result<HashMap<K, V>, HydrateError>
    where
        D: ReadDoc,
        K: FromStr + Eq + Hash,
        K::Err: ToString,
        V: Hydrate,
    {
        let obj = match doc.get(obj, &prop)? {
            Some((Value::Object(ObjType::Map), id)) => id,
            _ => {
                return Err(HydrateError::unexpected(
                    "a map",
                    "something else".to_string(),
                ))
            }
        };
        let Some(obj_type) = doc.object_type(&obj) else {
            return Err(HydrateError::unexpected("a map", "a scalar value".to_string()))
        };
        match obj_type {
            ObjType::Map | ObjType::Table => {
                let mut result = HashMap::new();
                for (key, _, _) in doc.map_range(&obj, ..) {
                    let column_key = K::from_str(key).map_err(|err| {
                        // FIXME this is an abuse of `Unexpected::Other`
                        HydrateError::Unexpected(autosurgeon::hydrate::Unexpected::Other {
                            expected: err.to_string(),
                            found: key.to_owned(),
                        })
                    })?;
                    let val = V::hydrate(doc, &obj, key.into())?;
                    result.insert(column_key, val);
                }
                Ok(result)
            }
            ObjType::Text => Err(HydrateError::unexpected(
                "a map",
                "a text object".to_string(),
            )),
            ObjType::List => Err(HydrateError::unexpected(
                "a map",
                "a list object".to_string(),
            )),
        }
    }
}

If you think that's a good thing to have in autosurgeon, I'm happy to make a PR. I think I'd also need to add a variant HydrateError::Parse(Box<dyn Error>) or something akin to that, which would wrap a parse error from FromStr.

`#[derive(Reconcile)]` produces invalid code if enum has variants with both one and multiple tuple fields

To reproduce:

#[derive(Reconcile)]
pub enum Ports {
    Range(u16, u16),
    Collection(Vec<u16>),
}

gives the following error:

error: expected identifier, found `,`
   --> autosurgeon-derive/tests/reconcile.rs:166:10
    |
166 | #[derive(Reconcile)]
    |          ^^^^^^^^^
    |          |
    |          expected identifier
    |          help: remove this comma
167 | pub enum Ports {
    |          ----- while parsing this enum
    |
    = note: this error originates in the derive macro `Reconcile` (in Nightly builds, run with -Z macro-backtrace for more info)

Workaround: declare the single-field variant as a struct variant.

Reconcile fails to remove an element from a map

I have a use-case where I need to remove an element from a HashMap field of a Reconcile struct, and have the reconcile process remove that element from the underlying Automerge doc.

Repro

in automerge/src/reconcile/map.rs, changing this test:

    #[test]
    fn reconcile_map() {
        let mut map = HashMap::new();
        map.insert("key1", vec!["one", "two"]);
        map.insert("key2", vec!["three"]);
        let mut doc = automerge::AutoCommit::new();
        reconcile(&mut doc, &map).unwrap();
        assert_doc!(
            doc.document(),
            map! {
                "key1" => { list! { {"one"}, {"two"} }},
                "key2" => { list! { {"three"} }},
            }
        );

        // Added the following:
        map.remove("key1");
        reconcile(&mut doc, &map).unwrap();
        assert_doc!(
            doc.document(),
            map! {
                "key2" => { list! { {"three"} }},
            }
        );
    }

fails with:

thread 'reconcile::map::tests::reconcile_map' panicked at 'documents didn't match
 expected
{
  "key2": [
    [
      [
        "three"
      ]
    ]
  ]
}
 got
{
  "key1": [
    [
      [
        "one"
      ],
      [
        "two"
      ]
    ]
  ],
  "key2": [
    [
      [
        "three"
      ]
    ]
  ]
}'

Save/load support

Is there an example of saving and loading from an existing automerge binary? I've been trying to do something like this but the types seem to be mismatched.

let mut doc = automerge::AutoCommit::new(); // `AutoCommitWithObs<UnObserved>` type

let saved_doc = doc.save();

let mut loaded_doc = Automerge::load(&saved_doc).unwrap(); // `Automerge` type

let test_new = TestStruct { a: 1 }

reconcile(&mut loaded_doc, test_new); // but reconcile accepts `AutoCommitWithObs<UnObserved>` not `Automerge`
error: the trait bound `automerge::Automerge: Transactable` is not satisfied
label: required by a bound introduced by this call

note: required for `automerge::Automerge` to implement `Doc`
label: required by a bound introduced by this call

note: required by a bound in `autosurgeon::reconcile`
label: required by a bound introduced by this call

Can't use reconcile/hydrate with an Automerge Transaction due to StaleHeads

Here is some working code with AutoCommit

pub fn log_draft(editor: RustOpaque<Mutex<LocalEditor>>) -> Result<()> {
    let mut editor = editor.lock().unwrap();
    let LocalEditor { doc, .. } = &mut *editor;
    let mut demo: DemoSchema = hydrate(doc).unwrap();
    demo.draft
        .splice(0, demo.draft.as_str().chars().count() as isize, "");
    demo.edit_counter.increment(1);
    reconcile(doc, demo)?;
    Ok(())
}

If I change the type of the doc from AutoCommit to Automerge, and use a transaction:

    let mut editor = editor.lock().unwrap();
    let LocalEditor { doc, .. } = &mut *editor;
    let mut demo: DemoSchema = hydrate(doc).unwrap();
    demo.draft
        .splice(0, demo.draft.as_str().chars().count() as isize, "");
    demo.edit_counter.increment(1);
    let mut tx = doc.transaction();
    reconcile(&mut tx, demo)?;
    let _ = tx.commit();
    Ok(())
}```

I am seeing StaleHeads exception on a certain synchronization flow. 

I wish I had a clean repro, but I don't. I was seeing the error with the Automerge code, but not with the AutoCommit code, in the following scenario on a wasm build. I'm happy to work with you over Slack on chasing it down if you're interested.

Client 1: Make edit A, sync protocol until quiescent. (heads are now, say, ["1234"])
Client 2: Sync protocol, edit B, sync protocol (heads are now ["5678"])
Client 1: Edit C -> fails on reconcile with StaleHeads. Expected heads are ["9abc"], actual heads are ["9abc", "5678"]

Old key is not deleted when updating an enum between variants

When an instance of an enum is reconciled and the variant is changed, the key for the old variant is not deleted. If the new variant comes later than the old one, then this can cause the value to be hydrated as the wrong variant.

Failing test case and output:

---- reconcile_between_enum_variants stdout ----
thread 'reconcile_between_enum_variants' panicked at 'documents didn't match
 expected
{
  "temp": [
    {
      "Fahrenheit": [
        [
          [
            "three"
          ],
          [
            6.7
          ]
        ]
      ]
    }
  ]
}
 got
{
  "temp": [
    {
      "Celsius": [
        [
          [
            "one"
          ],
          [
            1.2
          ]
        ]
      ],
      "Fahrenheit": [
        [
          [
            "three"
          ],
          [
            6.7
          ]
        ]
      ]
    }
  ]
}', /home/felirovas/.cargo/registry/src/github.com-1ecc6299db9ec823/automerge-test-0.2.0/src/lib.rs:492:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Fix trait errors with overriden automerge

Follow-up on #31 (which I tried to re-open).

I am still having the problem when using my own local checked-out version of Automerge at 0.5.1, and Autosurgeon at 0.8.

Is there a reason why this would only happen when working with your own checked-out Automerge as a path dependency?

For example:

error[E0277]: the trait bound `Transaction<'_>: autosurgeon::ReadDoc` is not satisfied
   --> examples/distributed_bakery.rs:476:27
    |
476 |                 reconcile(&mut tx, &bakery).unwrap();
    |                 --------- ^^^^^^^ the trait `autosurgeon::ReadDoc` is not implemented for `Transaction<'_>`
    |                 |
    |                 required by a bound introduced by this call
    |
    = help: the following other types implement trait `autosurgeon::ReadDoc`:
              automerge::autocommit::AutoCommit
              automerge::automerge::Automerge
              automerge::transaction::manual_transaction::Transaction<'a>
    = note: required for `Transaction<'_>` to implement `Doc`
note: required by a bound in `autosurgeon::reconcile`

Autosurgeon reconcile not clearing all old data

I'm not sure the test below is 100% minimal, but I am seeing that the old key "discard" is not removed from the converted document.

automerge = "0.5.11"
autosurgeon = "0.8.3"
#[cfg(test)]
mod tests {
    use automerge::AutoCommit;
    use autosurgeon::{reconcile, Hydrate, Reconcile};

    pub fn debug_dump_doc<D>(doc: &D) -> String
    where
        D: automerge::ReadDoc,
    {
        serde_json::to_string_pretty(&automerge::AutoSerde::from(doc)).unwrap()
    }

    #[derive(Clone, Debug, Default, Hydrate, Reconcile)]
    pub struct V1Container {
        pub items: Vec<V1Item>,
    }

    #[derive(Clone, Debug, Default, Hydrate, Reconcile)]
    pub struct V1Item{
        pub keep: String,
        pub discard: String,
    }

    #[derive(Clone, Debug, Default, Hydrate, Reconcile)]
    pub struct V2Container {
        pub items: Vec<V2Item>,
    }

    #[derive(Clone, Debug, Default, Hydrate, Reconcile)]
    pub struct V2Item{
        pub keep: String,
    }

    pub fn convert_item(old: V1Item) -> V2Item {
        V2Item {
            keep: old.keep,
        }
    }

    pub fn convert_container(old: V1Container) -> V2Container {
        V2Container {
            items: old.items.into_iter().map(convert_item).collect(),
        }
    }

    fn make_test_v1() -> V1Container {
        V1Container {
            items: vec![
                V1Item {
                    keep: "keep".to_string(),
                    discard: "discard".to_string(),
                },
                V1Item {
                    keep: "keep".to_string(),
                    discard: "discard".to_string(),
                },
            ],
        }
    }

    #[test]
    fn test_container_with_items() {
        let orig = make_test_v1();

        let converted = convert_container(orig.clone());

        let mut doc = AutoCommit::new();
        reconcile(&mut doc, &orig).unwrap();
        reconcile(&mut doc, &converted).unwrap();
        let orig_dump = debug_dump_doc(&doc);

        let mut new_doc = AutoCommit::new();
        reconcile(&mut new_doc, converted.clone()).unwrap();

        let new_dump = debug_dump_doc(&new_doc);

        assert_eq!(orig_dump, new_dump);
    }
}

Optional or Default Hydration

I would like to support fowards-only migrations in an easier path.

I have a struct

#[derive(Reconcile, Hydrate)]
pub struct Person {
  #[key]
  pub id: Uuid,
  pub name: String,
 }

Which i have been using in my document.

I would like to start tracking emails, but I don't have them for the past.

I would hope that one of these would work, instead of an offline document migration.

#[derive(Reconcile, Hydrate)]
pub struct Person {
 #[key]
 pub id: Uuid,
 pub name: String,
 pub email: Option<String>
}
#[derive(Reconcile, Hydrate, Default)]
pub struct Person {
  #[key]
  pub id: Uuid,
  pub name: String,
  pub email: String
 }

Unexpected uint error with newtype wrapper

Hello!

I have a newtype wrapper around a u64 like so:

#[derive(Debug, Hydrate, Reconcile)]
pub struct Item {
    pub name: String,
    pub date: Option<Date>,
}

#[derive(Debug, Hydrate, Reconcile)]
pub struct Date(u64);

When I attempt to hydrate it from a doc with a value present for that key, I get the error unexpected uint.

I think this is because Option::hydrate calls Date::hydrate_uint but the derive impl for a newtype struct only generates Hydrate::hydrate. Is there a better way to accomplish what I'm trying to do here?

And as a larger meta question, why isn't the Hydrate impl for Option simply something like this that handles both missing and explicitly null values?

Ok(match doc.get(obj, &prop)? {
    Some((Scalar(v), _)) if *v == Null => None,
    Some(_) => Some(T::hydrate(doc, obj, prop)?),
    None    => None,
})

Add Reconcile to Set types

Currently autosurgeon supports map types and list types, but there's not implmenentations for set types. It would be great if all of std::collections was represented.

Support for foreign keys

I'd like to be able to reconcile / hydrate a related object as a foreign key in the document.

Currently, I'm running into a roadblock in the API:

In Reconcile::reconcile I'd need to be able to access the doc, to be able to determine the ObjId for the related object, in order to call reconcile_prop for it.

For example:

fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
    let mut m = reconciler.map()?;
    // ...
    let Found(related_key) = self.related.key() else {
        // ...
    };
    let related_key = related_key.to_string();
    m.put("related", related_key)?;
    // no access to `doc`, and need to get `related_obj_id` from `doc` as well
    reconcile_prop(&mut doc, related_obj_id, &*related_key, &self.related)?;
    // ...
    Ok(())
}

Fix trait errors with automerge

With automerge 0.5, when copying some structs from the example, I get errors like:

41  | #[derive(Debug, Clone, Reconcile, Hydrate, PartialEq)]
    |                                   ^^^^^^^ the trait `AsRef<automerge::exid::ExId>` is not implemented for `ObjId`
42  | struct Address {
43  |    line_one: String,
    |    ---------------- required by a bound introduced by this call

This goes away when going back to automerge 0.4

Observability API

Hello, while looking at the autosurgeon API I noticed that there isn't an equivalent of automerge's Observer API. It would be nice if there was an equivalent or at least documented examples of how to observe changes in autosurgeon.

Add historical hydrate

I'm currently wanting to use this for restoring values in a document both at the latest state as well as sometimes from historical versions, using _at queries with the list of heads.

I can't see how to use autosurgeon's hydrate for this but I may be missing something. If it is missing I'd be happy to have a go at implementing it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.