Code Monkey home page Code Monkey logo

mpl'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

mpl's People

Contributors

choxi avatar ept avatar orionz avatar pvh 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mpl's Issues

peer clocks don't reset when we change docIds

If Alice and Bob both open up the same document, and then Alice creates a new document, Alice keeps Bob's vector clock in deltaRouter.clocks even though its for the wrong docId now. You can see this by reproducing these steps and inspecting app.store.deltaRouter.clocks in the Alice's web inspector before and after we change documents -- we should theoretically not have any vector clocks stored because Bob has never opened the document that Alice is on but instead we do still have his vector clock.

I'm not sure how this would manifest as a bug in Trellis, but it could be causing problems especially in conjunction with #9.

Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER

screen shot 2017-06-21 at 15 23 17

Terminal logs:

✔ Launching Application
GVA info: Successfully connected to the Intel plugin, offline Gen9
[22989:0621/152136.419206:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.351121:ERROR:webrtcsession.cc(332)] Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
[22989:0621/152137.355237:ERROR:webrtcsession.cc(332)] Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
[22989:0621/152137.358229:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.361192:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.364230:ERROR:webrtcsession.cc(332)] Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
[22989:0621/152137.367950:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.370985:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.374608:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.509779:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.512939:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.518540:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.522236:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.525885:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.530349:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.533590:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.537311:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.540309:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152137.545200:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152142.843050:ERROR:webrtcsession.cc(332)] Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
[22989:0621/152142.880935:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152142.893182:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152143.014947:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
webrtcsession[22989:0621/152143.060083:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152143.073214:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152144.763035:ERROR:webrtcsession.cc(332)] Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
[22989:0621/152144.783546:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152144.796183:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152144.809755:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152144.892606:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.
[22989:0621/152144.906217:ERROR:webrtcsession.cc(1106)] ProcessIceMessage: ICE candidates can't be added without any remote session description.

getaddrinfo ENOTFOUND ash ash:3789

Bonjour connection over local network. Peers initially stream data but then it stopped working. Peers status remains green. Errors in console as follows:

screen shot 2017-06-20 at 16 31 12

ash is the hostname of the second peer (Natasha/c2ce).

Subscribing to stream of changeset events

An interesting usage pattern emerged in @orionz's MUD prototype. Sometimes, when a state change occurs (say, an object in the game falls to the ground with a thump), we want to not only re-render the portions of UI that depend on that bit of state (something that React handles well), but we also want to show some kind of notification event.

That notification event does not need to be part of the application state — in principle, the Tesseract document could contain a big long list of events that have occurred, but that's not really what we want, as the application would have to keep checking which of those events it has already seen. A more intuitive pattern would be to attach some metadata to the changeset itself, and for the application to subscribe to the stream of changesets being applied to the state.

At the moment, an application can watch for changesets coming in over the network by subscribing to APPLY_DELTAS actions. However, any changesets that originate in the local process do not go through that event loop; rather, when Tesseract.changeset() is called from a Redux reducer, it returns a new state with the changeset already applied, and doesn't offer any sensible way for the changeset to be observed. In principle, notifications for locally originated changes could be triggered directly in the reducer, but that would quickly get messy, as the state at the time of triggering that notification would not yet reflect the changeset.

Instead, maybe a better approach would route changesets through the same code path, regardless of whether they came in from the network or originated locally. That would perhaps imply that Tesseract.changeset() should return only a changeset (like a delta that is sent over the network), not a new state with that changeset already applied. The Redux-style reducer in aMPL could then handle locally originated changesets and deltas from the network in the same way: both would be applied to the store through an APPLY_DELTAS action, and both would be fed to any subscribers.

Not sure what exactly the best structure for this pattern would look like in aMPL, hence opening this issue to discuss.

interface for updating peer name

In Trellis, we're doing this to update our user's peer name:

  updatePeerName(value) {
    aMPL.config.name = value
    localStorage.setItem("peerName", value)

    // Force network reconnect
    this.props.store.dispatch({
      type: "OPEN_DOCUMENT",
      file: this.props.store.save()
    })
  }

It sets the shared config on aMPL, and then re-opens the current document as a hacky way of getting the network to reconnect and update all of our listeners in the omniview sidebar. It works for now, but something breaks if you change your peer name too many times too quickly.

`sendDeltasToPeer` sends old vector clock

I noticed a potential bug in sendDeltasToPeer:

diff --git a/src/ampl/network/delta-router.js b/src/ampl/network/delta-router.js
index cc6fb18..c4f9e37 100644
--- a/src/ampl/network/delta-router.js
+++ b/src/ampl/network/delta-router.js
@@ -86,7 +86,7 @@ export default class DeltaRouter {

         if (this.isAheadOf(myClock, theirEstimatedClock)) {
           console.log("We are ahead - send deltas",peer.id)
-          this.sendDeltasToPeer(peer)
+          this.sendDeltasToPeer(peer, theirEstimatedClock)
         }

         // it should be safe to use the estimated clock but for this purpose m.vectorClock would work too
@@ -114,15 +114,14 @@ export default class DeltaRouter {
   broadcastState() {
     console.log("broadcast state")
     this.peergroup.peers().forEach((peer) => {
-      this.sendDeltasToPeer(peer)
+      this.sendDeltasToPeer(peer, this.clocks[peer.id])
     })
   }

-  sendDeltasToPeer(peer) {
+  sendDeltasToPeer(peer, theirClock) {
     console.log("maybe send deltas")
     let state = this.getTesseractCB()
     let myClock = Tesseract.getVClock(state)
-    let theirClock = this.clocks[peer.id];

     if (theirClock) {

When it's called from the peer message handler, we calculate which deltas to send based on our peer's clock in this.clocks[peer.id], but we don't update this.clocks[peer.id] to the newest clock until after we send the deltas:

        // update the clock after sending to prevent exceptions above from falsely moving our
        // estimated peer clock forward
        this.clocks[peer.id] = theirEstimatedClock

That means we're probably sending deltas based on a stale clock value. Maybe we're just sending redundant deltas then, but it could be causing other problems as well.

ampl gripes if you don't have a SLACK_BOT_TOKEN set

require('ampl')
TypeError: The first argument to tesseract.inspect must be the object to modify, but you passed 0
at checkTarget (C:\Users\pvh\Source\bot\node_modules\tesseract\src\tesseract.js:162:11)
at Object.inspect (C:\Users\pvh\Source\bot\node_modules\tesseract\src\tesseract.js:241:3)
at formatValue (util.js:349:36)
at formatProperty (util.js:790:15)
at util.js:650:12
at Array.map (native)
at formatObject (util.js:649:15)
at formatValue (util.js:589:16)
at formatProperty (util.js:790:15)
at util.js:650:12
at Array.map (native)
at formatObject (util.js:649:15)
at formatValue (util.js:589:16)
at Object.inspect (util.js:183:10)
at REPLServer.self.writer (repl.js:461:19)
at finish (repl.js:586:38)

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.