Skip to main content

Automerge

This library provides the core automerge data structure and sync algorithms. Other libraries can be built on top of this one which provide IO and persistence.

An automerge document can be though of an immutable POJO (plain old javascript object) which automerge tracks the history of, allowing it to be merged with any other automerge document.

Creating and modifying a document

You can create a document with init or from and then make changes to it with change, you can merge two documents with merge.

import * as automerge from "@automerge/automerge"

type DocType = {ideas: Array<automerge.Text>}

let doc1 = automerge.init<DocType>()
doc1 = automerge.change(doc1, d => {
d.ideas = [new automerge.Text("an immutable document")]
})

let doc2 = automerge.init<DocType>()
doc2 = automerge.merge(doc2, automerge.clone(doc1))
doc2 = automerge.change<DocType>(doc2, d => {
d.ideas.push(new automerge.Text("which records it's history"))
})

// Note the `automerge.clone` call, see the "cloning" section of this readme for
// more detail
doc1 = automerge.merge(doc1, automerge.clone(doc2))
doc1 = automerge.change(doc1, d => {
d.ideas[0].deleteAt(13, 8)
d.ideas[0].insertAt(13, "object")
})

let doc3 = automerge.merge(doc1, doc2)
// doc3 is now {ideas: ["an immutable object", "which records it's history"]}

Applying changes from another document

You can get a representation of the result of the last change you made to a document with getLastLocalChange and you can apply that change to another document using applyChanges.

If you need to get just the changes which are in one document but not in another you can use getHeads to get the heads of the document without the changes and then getMissingDeps, passing the result of getHeads on the document with the changes.

Saving and loading documents

You can save a document to generate a compresed binary representation of the document which can be loaded with load. If you have a document which you have recently made changes to you can generate recent changes with saveIncremental, this will generate all the changes since you last called saveIncremental, the changes generated can be applied to another document with loadIncremental.

Viewing different versions of a document

Occasionally you may wish to explicitly step to a different point in a document history. One common reason to do this is if you need to obtain a set of changes which take the document from one state to another in order to send those changes to another peer (or to save them somewhere). You can use view to do this.

import * as automerge from "@automerge/automerge"
import * as assert from "assert"

let doc = automerge.from({
key1: "value1",
})

// Make a clone of the document at this point, maybe this is actually on another
// peer.
let doc2 = automerge.clone < any > doc

let heads = automerge.getHeads(doc)

doc =
automerge.change <
any >
(doc,
d => {
d.key2 = "value2"
})

doc =
automerge.change <
any >
(doc,
d => {
d.key3 = "value3"
})

// At this point we've generated two separate changes, now we want to send
// just those changes to someone else

// view is a cheap reference based copy of a document at a given set of heads
let before = automerge.view(doc, heads)

// This view doesn't show the last two changes in the document state
assert.deepEqual(before, {
key1: "value1",
})

// Get the changes to send to doc2
let changes = automerge.getChanges(before, doc)

// Apply the changes at doc2
doc2 = automerge.applyChanges < any > (doc2, changes)[0]
assert.deepEqual(doc2, {
key1: "value1",
key2: "value2",
key3: "value3",
})

If you have a view of a document which you want to make changes to you can clone the viewed document.

Syncing

The sync protocol is stateful. This means that we start by creating a SyncState for each peer we are communicating with using initSyncState. Then we generate a message to send to the peer by calling generateSyncMessage. When we receive a message from the peer we call receiveSyncMessage. Here's a simple example of a loop which just keeps two peers in sync.

let sync1 = automerge.initSyncState()
let msg: Uint8Array | null
;[sync1, msg] = automerge.generateSyncMessage(doc1, sync1)

while (true) {
if (msg != null) {
network.send(msg)
}
let resp: Uint8Array =
(network.receive()[(doc1, sync1, _ignore)] =
automerge.receiveSyncMessage(doc1, sync1, resp)[(sync1, msg)] =
automerge.generateSyncMessage(doc1, sync1))
}

Conflicts

The only time conflicts occur in automerge documents is in concurrent assignments to the same key in an object. In this case automerge deterministically chooses an arbitrary value to present to the application but you can examine the conflicts using getConflicts.

import * as automerge from "@automerge/automerge"

type Profile = {
pets: Array<{name: string, type: string}>
}

let doc1 = automerge.init<Profile>("aaaa")
doc1 = automerge.change(doc1, d => {
d.pets = [{name: "Lassie", type: "dog"}]
})
let doc2 = automerge.init<Profile>("bbbb")
doc2 = automerge.merge(doc2, automerge.clone(doc1))

doc2 = automerge.change(doc2, d => {
d.pets[0].name = "Beethoven"
})

doc1 = automerge.change(doc1, d => {
d.pets[0].name = "Babe"
})

const doc3 = automerge.merge(doc1, doc2)

// Note that here we pass `doc3.pets`, not `doc3`
let conflicts = automerge.getConflicts(doc3.pets[0], "name")

// The two conflicting values are the keys of the conflicts object
assert.deepEqual(Object.values(conflicts), ["Babe", Beethoven"])

Actor IDs

By default automerge will generate a random actor ID for you, but most methods for creating a document allow you to set the actor ID. You can get the actor ID associated with the document by calling getActorId. Actor IDs must not be used in concurrent threads of executiong - all changes by a given actor ID are expected to be sequential.

Listening to patches

Sometimes you want to respond to changes made to an automerge document. In this case you can use the PatchCallback type to receive notifications when changes have been made.

Cloning

Currently you cannot make mutating changes (i.e. call change) to a document which you have two pointers to. For example, in this code:

let doc1 = automerge.init()
let doc2 = automerge.change(doc1, d => (d.key = "value"))

doc1 and doc2 are both pointers to the same state. Any attempt to call mutating methods on doc1 will now result in an error like

Attempting to change an out of date document

If you encounter this you need to clone the original document, the above sample would work as:

let doc1 = automerge.init()
let doc2 = automerge.change(automerge.clone(doc1), d => (d.key = "value"))

The unstable module

We are working on some changes to automerge which are not yet complete and will result in backwards incompatible API changes. Once these changes are ready for production use we will release a new major version of automerge. However, until that point you can use the unstable module to try out the new features, documents from the unstable module are interoperable with documents from the main module. Please see the docs for the unstable module for more details.

Namespaces

Classes

Interfaces

References

Counter

Re-exports Counter


DelPatch

Re-exports DelPatch


Doc

Re-exports Doc


Float64

Re-exports Float64


IncPatch

Re-exports IncPatch


InsertPatch

Re-exports InsertPatch


Int

Re-exports Int


Patch

Re-exports Patch


PatchCallback

Re-exports PatchCallback


PutPatch

Re-exports PutPatch


SpliceTextPatch

Re-exports SpliceTextPatch


SyncMessage

Re-exports SyncMessage


Uint

Re-exports Uint

Type Aliases

ActorId

ฦฌ ActorId: string

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:1


ApplyOptions

ฦฌ ApplyOptions<T>: Object

Options passed to loadIncremental, applyChanges, and receiveSyncMessage

Type parameters

NameDescription
TThe type of value contained in the document

Type declaration

NameType
patchCallback?PatchCallback<T>

Defined in

automerge/javascript/src/stable.ts:74


AutomergeValue

ฦฌ AutomergeValue: ScalarValue | { [key: string]: AutomergeValue; } | AutomergeValue[] | Text

Defined in

automerge/javascript/src/types.ts:10


Change

ฦฌ Change: Uint8Array

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:3


ChangeFn

ฦฌ ChangeFn<T>: (doc: Extend<T>) => void

Type parameters

NameDescription
TThe type of value contained in the document This function may mutate doc

Type declaration

โ–ธ (doc): void

Function which is called by change when making changes to a Doc<T>

Parameters
NameType
docExtend<T>
Returns

void

Defined in

automerge/javascript/src/stable.ts:105


ChangeOptions

ฦฌ ChangeOptions<T>: Object

Options passed to change, and emptyChange

Type parameters

NameDescription
TThe type of value contained in the document

Type declaration

NameTypeDescription
message?stringA message which describes the changes
patchCallback?PatchCallback<T>A callback which will be called to notify the caller of any changes to the document
time?numberThe unix timestamp of the change (purely advisory, not used in conflict resolution)

Defined in

automerge/javascript/src/stable.ts:62


Conflicts

ฦฌ Conflicts: Object

The type of conflicts for particular key or index

Maps and sequences in automerge can contain conflicting values for a particular key or index. In this case getConflicts can be used to obtain a Conflicts representing the multiple values present for the property

A Conflicts is a map from a unique (per property or index) key to one of the possible conflicting values for the given property.

Index signature

โ–ช [key: string]: AutomergeValue

Defined in

automerge/javascript/src/stable.ts:568


DecodedChange

ฦฌ DecodedChange: Object

Type declaration

NameType
actorActorId
depsHeads
hashHash
messagestring | null
opsOp[]
seqnumber
startOpnumber
timenumber

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:74


DecodedSyncMessage

ฦฌ DecodedSyncMessage: Object

Type declaration

NameType
changesChange[]
haveSyncHave[]
headsHeads
needHeads

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:67


Extend

ฦฌ Extend<T>: T extends infer T[] ? List<Extend<T>> : T extends Object ? { [P in keyof T]: Extend<T[P]> } : T

To extend an arbitrary type, we have to turn any arrays that are part of the type's definition into Lists. So we recurse through the properties of T, turning any Arrays we find into Lists.

Type parameters

Name
T

Defined in

automerge/javascript/src/stable.ts:88


Heads

ฦฌ Heads: Hash[]

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:7


InitOptions

ฦฌ InitOptions<T>: Object

Options to be passed to init or load

Type parameters

NameDescription
TThe type of the value the document contains

Type declaration

NameTypeDescription
actor?ActorIdThe actor ID to use for this document, a random one will be generated if null is passed
freeze?boolean-
patchCallback?PatchCallback<T>A callback which will be called with the initial patch once the document has finished loading

Defined in

automerge/javascript/src/stable.ts:125


MaterializeValue

ฦฌ MaterializeValue: { [key: string]: MaterializeValue; } | MaterializeValue[] | Value

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:9


ObjID

ฦฌ ObjID: string

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:2


Prop

ฦฌ Prop: string | number

Defined in

node_modules/@automerge/automerge-wasm/index.d.ts:5


ScalarValue

ฦฌ ScalarValue: string | number | null | boolean | Date | Counter | Uint8Array

Defined in

automerge/javascript/src/types.ts:17

Variables

uuid

โ€ข Const uuid: UUIDFactory

Defined in

automerge/javascript/src/uuid.ts:14

automerge/javascript/src/uuid.ts:18

automerge/javascript/src/uuid.ts:22

Functions

applyChanges

โ–ธ applyChanges<T>(doc, changes, opts?): [Doc<T>]

Apply changes received from another document

doc will be updated to reflect the changes. If there are changes which we do not have dependencies for yet those will be stored in the document and applied when the depended on changes arrive.

You can use the ApplyOptions to pass a patchcallback which will be informed of any changes which occur as a result of applying the changes

Type parameters

Name
T

Parameters

NameType
docDoc<T>
changesUint8Array[]
opts?ApplyOptions<T>

Returns

[Doc<T>]

Defined in

automerge/javascript/src/stable.ts:696


change

โ–ธ change<T>(doc, options, callback?): Doc<T>

Update the contents of an automerge document

Example

A simple change

let doc1 = automerge.init()
doc1 = automerge.change(doc1, d => {
d.key = "value"
})
assert.equal(doc1.key, "value")

Example

A change with a message

doc1 = automerge.change(doc1, "add another value", d => {
d.key2 = "value2"
})

Example

A change with a message and a timestamp

doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
d.key2 = "value2"
})

Example

responding to a patch callback

let patchedPath
let patchCallback = patch => {
patchedPath = patch.path
}
doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
d.key2 = "value2"
})
assert.equal(patchedPath, ["key2"])

Type parameters

NameDescription
TThe type of the value contained in the document

Parameters

NameTypeDescription
docDoc<T>The document to update
optionsstring | ChangeOptions<T> | ChangeFn<T>Either a message, an ChangeOptions, or a ChangeFn
callback?ChangeFn<T>A ChangeFn to be used if options was a string Note that if the second argument is a function it will be used as the ChangeFn regardless of what the third argument is.

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:314


clone

โ–ธ clone<T>(doc, _opts?): Doc<T>

Make a full writable copy of an automerge document

Remarks

Unlike view this function makes a full copy of the memory backing the document and can thus be passed to change. It also generates a new actor ID so that changes made in the new document do not create duplicate sequence numbers with respect to the old document. If you need control over the actor ID which is generated you can pass the actor ID as the second argument

Type parameters

NameDescription
TThe type of the value contained in the document

Parameters

NameTypeDescription
docDoc<T>The document to clone
_opts?string | InitOptions<T>Either an actor ID to use for the new doc or an InitOptions

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:222


emptyChange

โ–ธ emptyChange<T>(doc, options): Doc<T>

Make a change to a document which does not modify the document

Type parameters

Name
T

Parameters

NameTypeDescription
docDoc<T>The doc to add the empty change to
optionsstring | void | ChangeOptions<T>Either a message or a ChangeOptions for the new change Why would you want to do this? One reason might be that you have merged changes from some other peers and you want to generate a change which depends on those merged changes so that you can sign the new change with all of the merged changes as part of the new change.

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:402


free

โ–ธ free<T>(doc): void

Explicity free the memory backing a document. Note that this is note necessary in environments which support FinalizationRegistry

Type parameters

Name
T

Parameters

NameType
docDoc<T>

Returns

void

Defined in

automerge/javascript/src/stable.ts:241


from

โ–ธ from<T>(initialState, _opts?): Doc<T>

Create an automerge document from a POJO

Example

const doc = automerge.from({
tasks: [
{description: "feed dogs", done: false}
]
})

Type parameters

NameTypeDescription
Textends Record<string, unknown>The type of the value passed to from and the type the resulting document will contain

Parameters

NameTypeDescription
initialStateT | Doc<T>The initial state which will be copied into the document
_opts?string | InitOptions<T>-

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:261


getActorId

โ–ธ getActorId<T>(doc): ActorId

Get the actor ID associated with the document

Type parameters

Name
T

Parameters

NameType
docDoc<T>

Returns

ActorId

Defined in

automerge/javascript/src/stable.ts:553


getAllChanges

โ–ธ getAllChanges<T>(doc): Change[]

Get all the changes in a document

This is different to save because the output is an array of changes which can be individually applied via applyChanges`

Type parameters

Name
T

Parameters

NameType
docDoc<T>

Returns

Change[]

Defined in

automerge/javascript/src/stable.ts:680


getChanges

โ–ธ getChanges<T>(oldState, newState): Change[]

Get the changes which are in newState but not in oldState. The returned changes can be loaded in oldState via applyChanges.

Note that this will crash if there are changes in oldState which are not in newState.

Type parameters

Name
T

Parameters

NameType
oldStateDoc<T>
newStateDoc<T>

Returns

Change[]

Defined in

automerge/javascript/src/stable.ts:668


getConflicts

โ–ธ getConflicts<T>(doc, prop): Conflicts | undefined

Get the conflicts associated with a property

The values of properties in a map in automerge can be conflicted if there are concurrent "put" operations to the same key. Automerge chooses one value arbitrarily (but deterministically, any two nodes who have the same set of changes will choose the same value) from the set of conflicting values to present as the value of the key.

Sometimes you may want to examine these conflicts, in this case you can use getConflicts to get the conflicts for the key.

Example

import * as automerge from "@automerge/automerge"

type Profile = {
pets: Array<{name: string, type: string}>
}

let doc1 = automerge.init<Profile>("aaaa")
doc1 = automerge.change(doc1, d => {
d.pets = [{name: "Lassie", type: "dog"}]
})
let doc2 = automerge.init<Profile>("bbbb")
doc2 = automerge.merge(doc2, automerge.clone(doc1))

doc2 = automerge.change(doc2, d => {
d.pets[0].name = "Beethoven"
})

doc1 = automerge.change(doc1, d => {
d.pets[0].name = "Babe"
})

const doc3 = automerge.merge(doc1, doc2)

// Note that here we pass `doc3.pets`, not `doc3`
let conflicts = automerge.getConflicts(doc3.pets[0], "name")

// The two conflicting values are the keys of the conflicts object
assert.deepEqual(Object.values(conflicts), ["Babe", Beethoven"])

Type parameters

Name
T

Parameters

NameType
docDoc<T>
propProp

Returns

Conflicts | undefined

Defined in

automerge/javascript/src/stable.ts:614


getHeads

โ–ธ getHeads<T>(doc): Heads

Get the hashes of the heads of this document

Type parameters

Name
T

Parameters

NameType
docDoc<T>

Returns

Heads

Defined in

automerge/javascript/src/stable.ts:889


getLastLocalChange

โ–ธ getLastLocalChange<T>(doc): Change | undefined

Get the binary representation of the last change which was made to this doc

This is most useful when staying in sync with other peers, every time you make a change locally via change you immediately call getLastLocalChange and send the result over the network to other peers.

Type parameters

Name
T

Parameters

NameType
docDoc<T>

Returns

Change | undefined

Defined in

automerge/javascript/src/stable.ts:637


getMissingDeps

โ–ธ getMissingDeps<T>(doc, heads): Heads

Get any changes in doc which are not dependencies of heads

Type parameters

Name
T

Parameters

NameType
docDoc<T>
headsHeads

Returns

Heads

Defined in

automerge/javascript/src/stable.ts:881


getObjectId

โ–ธ getObjectId(doc, prop?): ObjID | null

Return the object ID of an arbitrary javascript value

This is useful to determine if something is actually an automerge document, if doc is not an automerge document this will return null.

Parameters

NameType
docany
prop?Prop

Returns

ObjID | null

Defined in

automerge/javascript/src/stable.ts:649


init

โ–ธ init<T>(_opts?): Doc<T>

Create a new automerge document

Type parameters

NameDescription
TThe type of value contained in the document. This will be the type that is passed to the change closure in change

Parameters

NameTypeDescription
_opts?string | InitOptions<T>Either an actorId or an InitOptions (which may contain an actorId). If this is null the document will be initialised with a random actor ID

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:157


isAutomerge

โ–ธ isAutomerge(doc): boolean

Parameters

NameType
docunknown

Returns

boolean

Defined in

automerge/javascript/src/stable.ts:909


load

โ–ธ load<T>(data, _opts?): Doc<T>

Load an automerge document from a compressed document produce by save

Type parameters

NameDescription
TThe type of the value which is contained in the document. Note that no validation is done to make sure this type is in fact the type of the contained value so be a bit careful

Parameters

NameTypeDescription
dataUint8ArrayThe compressed document
_opts?string | InitOptions<T>Either an actor ID or some InitOptions, if the actor ID is null a random actor ID will be created Note that load will throw an error if passed incomplete content (for example if you are receiving content over the network and don't know if you have the complete document yet). If you need to handle incomplete content use init followed by loadIncremental.

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:444


loadIncremental

โ–ธ loadIncremental<T>(doc, data, opts?): Doc<T>

Load changes produced by saveIncremental, or partial changes

Type parameters

NameDescription
TThe type of the value which is contained in the document. Note that no validation is done to make sure this type is in fact the type of the contained value so be a bit careful

Parameters

NameTypeDescription
docDoc<T>-
dataUint8ArrayThe compressedchanges
opts?ApplyOptions<T>an ApplyOptions This function is useful when staying up to date with a connected peer. Perhaps the other end sent you a full compresed document which you loaded with load and they're sending you the result of getLastLocalChange every time they make a change. Note that this function will succesfully load the results of save as well as getLastLocalChange or any other incremental change.

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:487


merge

โ–ธ merge<T>(local, remote): Doc<T>

Merge local into remote

Type parameters

NameDescription
TThe type of values contained in each document

Parameters

NameTypeDescription
localDoc<T>The document to merge changes into
remoteDoc<T>The document to merge changes from

Returns

Doc<T>

  • The merged document

Often when you are merging documents you will also need to clone them. Both arguments to merge are frozen after the call so you can no longer call mutating methods (such as change) on them. The symtom of this will be an error which says "Attempting to change an out of date document". To overcome this call clone on the argument before passing it to merge.

Defined in

automerge/javascript/src/stable.ts:535


save

โ–ธ save<T>(doc): Uint8Array

Export the contents of a document to a compressed format

Type parameters

Name
T

Parameters

NameTypeDescription
docDoc<T>The doc to save The returned bytes can be passed to load or loadIncremental

Returns

Uint8Array

Defined in

automerge/javascript/src/stable.ts:516


view

โ–ธ view<T>(doc, heads): Doc<T>

Make an immutable view of an automerge document as at heads

Remarks

The document returned from this function cannot be passed to change. This is because it shares the same underlying memory as doc, but it is consequently a very cheap copy.

Note that this function will throw an error if any of the hashes in heads are not in the document.

Type parameters

NameDescription
TThe type of the value contained in the document

Parameters

NameTypeDescription
docDoc<T>The document to create a view of
headsHeadsThe hashes of the heads to create a view at

Returns

Doc<T>

Defined in

automerge/javascript/src/stable.ts:197

sync

decodeSyncState

โ–ธ decodeSyncState(state): SyncState

Decode some binary data into a SyncState

Parameters

NameType
stateUint8Array

Returns

SyncState

Defined in

automerge/javascript/src/stable.ts:771


encodeSyncState

โ–ธ encodeSyncState(state): Uint8Array

encode a SyncState into binary to send over the network

Parameters

NameType
stateSyncState

Returns

Uint8Array

Defined in

automerge/javascript/src/stable.ts:759


generateSyncMessage

โ–ธ generateSyncMessage<T>(doc, inState): [SyncState, SyncMessage | null]

Generate a sync message to send to the peer represented by inState

Type parameters

Name
T

Parameters

NameTypeDescription
docDoc<T>The doc to generate messages about
inStateSyncStateThe SyncState representing the peer we are talking to

Returns

[SyncState, SyncMessage | null]

An array of [newSyncState, syncMessage | null] where newSyncState should replace inState and syncMessage should be sent to the peer if it is not null. If syncMessage is null then we are up to date.

Defined in

automerge/javascript/src/stable.ts:789


initSyncState

โ–ธ initSyncState(): SyncState

Create a new, blank SyncState

When communicating with a peer for the first time use this to generate a new SyncState for them

Returns

SyncState

Defined in

automerge/javascript/src/stable.ts:854


receiveSyncMessage

โ–ธ receiveSyncMessage<T>(doc, inState, message, opts?): [Doc<T>, SyncState, null]

Update a document and our sync state on receiving a sync message

Type parameters

Name
T

Parameters

NameTypeDescription
docDoc<T>The doc the sync message is about
inStateSyncStateThe SyncState for the peer we are communicating with
messageUint8ArrayThe message which was received
opts?ApplyOptions<T>Any ApplyOptions, used for passing a PatchCallback which will be informed of any changes in doc which occur because of the received sync message.

Returns

[Doc<T>, SyncState, null]

An array of [newDoc, newSyncState, syncMessage | null] where newDoc is the updated state of doc, newSyncState should replace inState and syncMessage should be sent to the peer if it is not null. If syncMessage is null then we are up to date.

Defined in

automerge/javascript/src/stable.ts:817