Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repo updates #37

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "automerge"]
path = automerge
url = https://github.com/automerge/automerge-rs.git
[submodule "automerge-repo"]
path = automerge-repo
url = https://github.com/automerge/automerge-repo
1 change: 0 additions & 1 deletion automerge
Submodule automerge deleted from 89a086
7 changes: 6 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
---
sidebar_position: 1000
---
# Other APIs
# API Docs

* [`@automerge/automerge`](http://automerge.org/automerge/api-docs/js)
* [`@automerge/automerge-repo`](http://automerge.org/automerge-repo/)

# Other Languages

Here you can find links to the APIs for each implementation.

Expand Down
54 changes: 54 additions & 0 deletions docs/concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
sidebar_position: 2
---

# Concepts

:::info

This documentation is mostly focused on the javascript implementation of automerge. Some things will translate to other languages but some things - in particular the "repository" concept and `automerge-repo` library - will not.

:::


## Core concepts

Using automerge means storing your data in automerge [documents](#Documents). Documents have a [URL](#Document-URLs)s which you can use to share or request documents with/from other peers using a [repository](#Repositories). Repositories give you [`DocHandle`](#dochandles)s which you use to make changes to the document and listen for changes from other peers.

Automerge as used in javascript applications is actually a composition of two libraries. [`automerge-repo`](https://www.npmjs.com/package/@automerge/automerge-repo) which provides the networking and storage plumbing, and [`automerge`](https://www.npmjs.com/package/@automerge/automerge) which provides the CRDT implementation, a transport agnostic [sync protocol](#sync-protocol), and a compressed [storage format](#storage-format) which `automerge-repo` uses to implement various networking and storage plugins.

### Documents

A document is the "unit of change" in automerge. It's like a combination of a JSON object and a git repository. What does that mean?

Like a JSON object an automerge document is a map from strings to values, where the values can themselves be maps, arrays, or simple types like strings or numbers. See the [data model](./data_model.md) section for more details.

Like a git repository an automerge document has a history made up of commits. Every time you make a change to a document you are adding to the history of the document. The combination of this history and some rules about how to handle conflicts means that any two automerge documents can always be merged. See [merging](./merge_rules.md') for the gory details.

### Repositories

A repository manages connections to remote peers and access to some kind of local storage. Typically you create a repository at application startup and then inject it into the parts of your application which need it. The repository gives out `DocHandle`s, which allow you to access the current state of a document and make changes to it without thinking about how to store those changes, transmit them to others, or fetch changes from others.

Networking and storage for a repository is pluggable. There are various ready made network transports and storage implementations but it is also easy to build your own.

### DocHandles

A `DocHandle` is an object returned from the various methods on a repository which create or request a document. The `DocHandle` has methods on it to access the underlying automerge document and to create new changes which are stored locally and transmitted to connected peers.

### Document URLs

Documents in a repository have a URL. An automerge URL looks like this:

```
automerge:2akvofn6L1o4RMUEMQi7qzwRjKWZ
```

That is, a string of the form `automerge:<base58>`. This URL can be passed to a repository which will use it to check if the document is in any local storage or available from any connected peers.

### Sync Protocol

Repositories communicate with each other using an efficient sync protocol which is implmented in `automerge`. This protocol is transport agnostic and works on a per-document basis, a lot of the work `automerge-repo` does is handling running this sync protocol for multiple documents over different kinds of network.

### Storage Format

`automerge` implements a compact binary storage format which makes it feasible to store all the editing history of a document (for example, storing every keystroke in a large text document). `automerge-repo` implements the common logic of figuring out when to compress documents and doing so in a way which is safe for concurrent reads and writes.
49 changes: 3 additions & 46 deletions docs/cookbook/modeling-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,11 @@ In this section, we will discuss how to model data within a particular document,

## How many documents?

You can decide which things to group together as one Automerge document (more fine grained or more coarse grained) based on what makes sense in your app. Having hundreds of docs should be fine — we've built prototypes of that scale. One major automerge project, [PushPin](https://github.com/automerge/pushpin), was built around very granular documents. This had a lot of benefits, but the overhead of syncing many thousands of documents was high. One of the first challenges in synchronizing large numbers of documents is that nodes are likely to have overlapping but disjoint documents and neither side wants to disclose things the other doesn't know about (at least in our last system, knowing the ID of a document was evidence a client should have access to it.)
You can decide which things to group together as one Automerge document (more fine grained or more coarse grained) based on what makes sense in your app. Having hundreds of docs should be fine — we've built prototypes of that scale. One major automerge project, [PushPin](https://github.com/automerge/pushpin), was built around very granular documents. This had a lot of benefits, but the overhead of syncing many thousands of documents was high.

We believe on the whole there's an art to the granularity of data that is universal. When should you have two JSON documents or two SQLite databases or two rows? We suspect that an Automerge document is best suited to being a unit of collaboration between two people or a small group.


## TypeScript support

Given that you have a document, how can you create safety rails for its data integrity? In a typical SQL database, a table would have its own schema, and you create migrations from one schema version to the next. Automerge is flexible on the schema, and will let you add and remove properties and values at will. To improve the programming experience, a document can be typed to have its own schema using TypeScript.

```js
type D = {
count: Automerge.Counter,
text: Automerge.Text,
cards: string[]
}
let doc = Automerge.change<D>(Automerge.init(), (doc: D) => {
doc.count = new Automerge.Counter()
doc.text = new Automerge.Text()
// Note that we have to wrap the array in Automerge.List. This is fine because
// automerge immediately wraps the underlying array in a proxy object
// conforming to `Automerge.List
doc.cards = [] as unknown as Automerge.List<string>
})
```

## Setting up an initial document structure

When you create a document using `Automerge.init()`, it's just an empty JSON document with no properties. As the first change, most applications will need to initialize some empty collection objects that are expected to be present within the document.
Expand Down Expand Up @@ -67,39 +47,16 @@ doc2 = Automerge.merge(doc2, doc1)

However, sometimes it's inconvenient to have to sync the initial change to a device before you can modify the document on that device. If you want two devices to be able to independently set up their own document schema, but still to be able to merge those documents, you have to be careful. Simply doing `Automerge.change()` on each device to initialize the schema **will not work**, because you now have two different documents with no shared ancestry (even if the initial change performs the same operations, each device has a different actorId and so the changes will be different).

If you really must initialize each device's copy of a document independently, there are some hacks you can use. One option is to do the initial `Automerge.change()` once to set up your schema, then call `Automerge.getLastLocalChange()` on the document (which returns a byte array), and *hard-code that byte array into your application*. Now, on each device that needs to initialize a document, you do this:
If you really must initialize each device's copy of a document independently, one option is to do the initial `Automerge.change()` once to set up your schema, then call `Automerge.save()` on the document (which returns a byte array), and *hard-code that byte array into your application*. Now, on each device that needs to initialize a document, you do this:

```js
// hard-code the initial change here
const initChange = new Uint8Array([133, 111, 74, 131, ...])
let [doc] = Automerge.applyChanges(Automerge.init(), [initChange])
let [doc] = Automerge.load(initChange)
```

This will set you up with a document whose initial change is the one you hard-coded. Any documents you set up with the same initial change will be able to merge.

There is an alternative hack you can use, if you know what you are doing (be careful, this can easily go wrong). Instead of hard-coding a byte array, you can create a change with a hard-coded actorId and a hard-coded timestamp. If two devices perform exactly identical operations with the same actorId and the same timestamp, they will generate changes that are byte-for-byte identical, and which therefore will have the same hash. This way, you can also set up documents with the same initial change.

To hard-code the actorId and timestamp, you can use the following code:

```js
let schema = Automerge.change(Automerge.init({actorId: '0000'}), {time: 0}, doc => {
doc.count = new Automerge.Counter()
doc.text = new Automerge.Text()
doc.cards = []
})
let initChange = Automerge.getLastLocalChange(schema)
```

Now `initChange` is a byte array as before. You cannot use the document `schema` for any further changes, because it has a fixed actorId, and it's an error to have multiple users with the same actorId. Instead, you can now make a new document with `Automerge.init()` and `initChange` as before:

```js
let [doc] = Automerge.applyChanges(Automerge.init(), [initChange])
```

Now, `doc` is initialized and ready to be used as any other Automerge document. You can save that document to disk as you would normally with `Automerge.save(doc)` and load it later when your app starts.

> NOTE: You only have to create this initial change the first time the document loads. You can check if you have a local document already before making this initial document.

## Versioning

Often, there comes a time in the production lifecycle where you will need to change the schema of a document. Because Automerge uses a JSON document model, it's similar to a NoSQL database, where properties can be arbitrarily removed and added at will.
Expand Down
69 changes: 0 additions & 69 deletions docs/cookbook/persistence.md

This file was deleted.