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

Request of helpfull hints : Automerge Sync Protocol Implementation #437

Open
raphael10-collab opened this issue Oct 2, 2021 · 8 comments

Comments

@raphael10-collab
Copy link

raphael10-collab commented Oct 2, 2021

In the attempt to re-write https://github.com/cudr/slate-collaborative/blob/master/packages/backend/src/AutomergeBackend.ts
to make it fit with the latest automerge version changes,
I'm trying to implement what has been outlined here: https://github.com/automerge/automerge/blob/main/SYNC.md

This is what I've done so far (forgive me for eventual errors):

interface peer_docId_hash {
  [docId: string]: string | Automerge.SyncState
}

interface syncStatesType {
  [peerId: string]: peer_docId_hash // a hash of [source][docId] containing in-memory sync state
}

type docsHandledByPeer = string[]

interface peerType {
  peerId: string,
  docs?: docsHandledByPeer,
}

class AutomergeBackend {
  peers: peerType[] // peers discovered by hyperswarm listening to the same "subject"

  syncStates: syncStatesType = {}

  backends = {} // a hash by [docId] of current backend values : ??????????

  syncPeersListFeed = (peer: peerType): syncStatesType => {
    let sst: syncStatesType = {}

    let peersKeysList = Object.keys(this.peers[0])

    for (let j = 0; j < this.peers.length; j++) {
      let peer_j= this.peers[j]
      let peer_j_0 = peer_j[peersKeysList[0]]
      sst[peer_j_0] = {}
    }
    return sst

  constructor(peers) {
    this.peers = peers
    this.syncStates = this.syncPeersListFeed(this.peers[0])
  }


  peerPrint (peer: peerType) {
    //console.log(".........................")

    let peersKeysList = Object.keys(this.peers[0])

    for (let i = 0; i < peersKeysList.length; i++) {
      console.log("peer[peersKeysList[" + i + "]]: ", peer[peersKeysList[i]])
    }
    //Object.keys(peer).forEach(key => {
      //console.log(peer[key])
    //})
  }

  random_id = () => { // it mimics hash function
    return Math.random().toString(36).substr(2);
  }

  syncPeerDocsFeed = (peer: peerType): void => {

    let peersKeysList = Object.keys(this.peers[0])

    let peer_docsListLength = peer[peersKeysList[1]].length;

    for (let j = 0; j < peer_docsListLength; j++) {
      this.syncStates[peer[peersKeysList[0]]][peer[peersKeysList[1]][j]] = this.random_id()
    }
  }
  initSync() {

      // https://github.com/automerge/automerge/blob/main/SYNC.md#connecting-decodesyncstate-or-initsyncstate
      // When a peer is discovered, first create a new syncState via initSyncState(),
      // and store the result somewhere associated with that peer.
      // All subsequent sync operations with that peer will return a new syncState to replace the previous one


    for (let i = 0; i < this.peers.length; i++) {
      this.syncPeerDocsFeed(this.peers[i])
    }

    console.log("syncStates : ", this.syncStates)
  }
}

export default AutomergeBackend


import AutomergeBackend from './AutomergeBackend'

type docsHandledByPeer = string[]

interface peerType {
  peerId: string,
  docs?: docsHandledByPeer,
}

let peers: peerType[] = [
  {
    peerId: "alice",
    docs: ["doc1", "doc2"]
  },
  {
    peerId: "bob",
    docs: ["doc1"]
  }
]

let automerge_backend = new AutomergeBackend(peers)
automerge_backend.initSync()

Executing it I get :

syncStates :  {
  alice: { doc1: 't2ulsx74d4', doc2: 'b48rqrvilp7' },
  bob: { doc1: 'dznzlmbcxk6' }
}

I also added updatePeers as class method:

  // https://github.com/automerge/automerge/blob/main/SYNC.md#synchronizing-with-one-or-more-peers
  updatePeers(docId: string) {
    Object.entries(this.syncStates).forEach(([peer, syncState]) => {
      const [nextSyncState, syncMessage] = Automerge.Backend.generateSyncMessage(
        this.backends[docId],
        syncState[docId] || Automerge.Backend.initSyncState(),
      )
      this.syncStates[peer] = { ...this.syncStates[peer], [docId]: nextSyncState }
    })
  }

Is, according to you, my implementation of syncStates reasonable and fully correct?
I looked for clues about syncState interface but I didn't find any:

https://github.com/automerge/automerge/blob/d82f6208a485376fc07b18dda43af8dfa2fde7c2/%40types/automerge/index.d.ts#L201

How would you suggest me to implement backends ?

@raphael10-collab
Copy link
Author

I've found this automerge-demo: https://github.com/pvh/automerge-demo
I'm going to dive into it. And will come back

@pvh
Copy link
Member

pvh commented Oct 4, 2021

Ah good, @raphael10-collab. I'd seen your message but wasn't at my desk over the weekend. Feel free to ping me here or on the automerge Slack with questions and I'll try to help you along.

@raphael10-collab
Copy link
Author

raphael10-collab commented Oct 5, 2021

I'm trying to port this automerge-demo to a react-typescript environment.

As starting point, I just imported in a brand new react-typescript app the automerge-demo's files.

Typescript checking I'm getting these errors:

  [12:13:55 PM] Starting compilation in watch mode...
  
  src/automerge-store.ts:4:29 - error TS2307: Cannot find module './worker.ts?worker' or its corresponding type declarations.
  
  4 import AutomergeWorker from './worker.ts?worker'
                                ~~~~~~~~~~~~~~~~~~~~
  
  src/automerge-store.ts:5:31 - error TS2307: Cannot find module './shared-worker.ts?worker' or its corresponding type declarations.
  
  5 import PersistenceWorker from './shared-worker.ts?worker'
                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  src/automerge-store.ts:32:5 - error TS2552: Cannot find name 'update'. Did you mean 'Date'?
  
  32     update((doc) => {
         ~~~~~~
  
    node_modules/typescript/lib/lib.es5.d.ts:907:13
      907 declare var Date: DateConstructor;
                      ~~~~
      'Date' is declared here.
  
  src/automerge-store.ts:32:13 - error TS7006: Parameter 'doc' implicitly has an 'any' type.
  
  32     update((doc) => {
                 ~~~
  
  src/automerge-store.ts:50:14 - error TS2552: Cannot find name 'update'. Did you mean 'Date'?
  
  50       } else update((doc) => Frontend.applyPatch(doc, message.patch))
                  ~~~~~~
  
    node_modules/typescript/lib/lib.es5.d.ts:907:13
      907 declare var Date: DateConstructor;
                      ~~~~
      'Date' is declared here.
  
  src/automerge-store.ts:50:22 - error TS7006: Parameter 'doc' implicitly has an 'any' type.
  
  50       } else update((doc) => Frontend.applyPatch(doc, message.patch))
                          ~~~
  
  src/db.ts:19:32 - error TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'.
    Type 'null' is not assignable to type 'string'.
  
  19           db.deleteObjectStore(storeNames.item(i))
                                    ~~~~~~~~~~~~~~~~~~
  
  src/utils.ts:20:21 - error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
  
  20     const context = this
                         ~~~~
  
    src/utils.ts:19:10
      19   return function (...args: any[]) {
                  ~~~~~~~~
      An outer value of 'this' is shadowed by this container.
  
  src/worker.ts:8:21 - error TS2304: Cannot find name 'WorkerGlobalScope'.
  
  8 declare const self: WorkerGlobalScope
                        ~~~~~~~~~~~~~~~~~
  
  [12:13:57 PM] Found 9 errors. Watching for file changes.

@corwin-of-amber
Copy link

I too want to try the new preview version of automerge 1.0.0, and would appreciate some simple working example. I used DocSet and Connection in the past so I'm familiar with the idea. What I want to see is a single snippet where you:

  • Initialize a document (well that I already know)
  • Initialize a (new) sync state
  • Change the document
  • Generate the sync messages corresponding to the change
  • Apply the changes to a separate copy of the document.

Also, Connection used to know when changes occur in the document without the user having to initiate synchronization, and DocSet had a registerHandler which was most convenient for this purpose. Is there a replacement for this functionality?

@ept
Copy link
Member

ept commented Oct 12, 2021

@raphael10-collab Hopefully @pvh can help you with the automerge-demo.

Hi @corwin-of-amber, you can look at the tests and the sync protocol docs for some self-contained examples.

Also, Connection used to know when changes occur in the document without the user having to initiate synchronization, and DocSet had a registerHandler which was most convenient for this purpose. Is there a replacement for this functionality?

Do you want to receive a callback whenever a document changes? You can use the Observable API for this.

@douira
Copy link

douira commented Oct 12, 2021

Does https://github.com/pvh/automerge-demo use the latest version of Automerge? I've also been unsure about which version all the other demo projects using Automerge were using and if the API they are all using is still the most current one.

@pvh
Copy link
Member

pvh commented Oct 12, 2021 via email

@corwin-of-amber
Copy link

Thanks @ept! These would be helpful pointers. I assumed the Observable API would come in handy from the name, so I will follow the examples in the tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants