Automerge Repo

repository·main·Indexed 20 days ago

https://github.com/automerge/automerge-repo

A management layer for Automerge CRDTs designed for multi-document workflows with pluggable storage and networking. It includes support for various environments through packages like @automerge/react for React hooks, @automerge/automerge-repo-solid-primitives for SolidJS, and network adapters for WebSockets, BroadcastChannel, and MessageChannel.

Tokens
34.2K
Snippets
103
Records
157
Agent score
71%

What's inside Automerge Repo

  1. Overview of Automerge Repo packages

    main

    The repository is organized into several functional categories:

    Core

    • automerge-repo: The central library that handles event dispatching and shared logic, such as peer connection decisions and storage write triggers.

    Front-end Adapters

    • @automerge/automerge-repo-react-hooks: React hooks for integrating with Automerge Repo.
    • @automerge/automerge-repo-svelte-store: A custom Svelte store.
    • @automerge/automerge-repo-solid-primitives: Primitives for Solid JS.

    Storage Adapters

    • @automerge/automerge-repo-storage-indexeddb: Persists data within a browser using IndexedDB.
    • @automerge/automerge-repo-storage-nodefs: Writes changes to the local filesystem (Node.js).

    Network Adapters

    • @automerge/automerge-repo-network-websocket: Enables client/server communication over WebSockets.
    • @automerge/automerge-repo-network-messagechannel: Uses the Web MessageChannel API for communication between browser tabs.
    • @automerge/automerge-repo-network-broadcastchannel: Provides tab-to-tab synchronization via the BroadcastChannel API.
  2. Overview of Automerge-Repo Network: Websocket

    main

    The @automerge/automerge-repo-network-websocket package provides both a Websocket client and a Websocket server implementation. These implementations are used to facilitate synchronization between peers using the WebSocket transport.

    Key characteristics:

    • Isomorphic: Uses isomorphic-ws to allow the client code to run in both Node.js and browser environments.
    • Server Environment: The server implementation is Node.js only.
    • Protocol: Uses a specific wire protocol involving a handshake phase followed by a sync phase for exchanging document state and ephemeral messages.
  3. Use @automerge/automerge-repo-network-message for cross-tab communication

    main
    The @automerge/automerge-repo-network-message package provides a network adapter for automerge-repo that enables synchronization between different browser tabs. It leverages the browser's MessageChannel API to facilitate communication between these tabs, allowing multiple instances of a repository to stay in sync within the same browser session.
  4. What is Automerge Repo?

    main
    Automerge Repo is a wrapper for the Automerge CRDT library. It provides high-level facilities for managing multiple documents simultaneously and features a pluggable architecture for both networking and storage. This allows developers to choose how data is persisted and how peers communicate.
  5. How Automerge Repo works

    main

    Automerge Repo is a wrapper for the Automerge CRDT library. It manages multiple documents simultaneously and provides a pluggable architecture for networking and storage.

    The library is built around two primary abstractions:

    1. Repo: The central coordinator. It handles event dispatching, manages storage, and decides how to connect to peers via network adapters.
    2. DocHandle: A wrapper around an individual Automerge.Doc. It is used to interact with a specific document, allowing you to read its current state or apply changes that will be synchronized across the network.
  6. Customize the Share Policy

    main

    The sharePolicy determines which documents are automatically shared with connecting peers. By default, all documents are shared with all peers.

    To restrict sharing, provide a custom function to the Repo constructor. This function receives the peerId and documentId and must return a Promise<boolean> indicating if the document should be shared.

    Note: The share policy does not prevent a peer from requesting a document if they already know its DocumentId.

  7. Understand Svelte Counter Demo Syncing Behavior

    main

    The Svelte Counter demo illustrates several Automerge Repo capabilities:

    • Cross-tab Syncing: When you open the same URL (including the #hash) in multiple tabs, updates in one tab are reflected in others. This is achieved via the @automerge/automerge-repo-network-broadcastchannel package.
    • Persistence: Refreshing the page reloads the document from local storage using @automerge/automerge-repo-storage-indexeddb.
    • Document Creation: Opening a URL without the #hash creates a brand new Automerge document with its own independent counter state.
    • Server-side Syncing: By running a sync server, documents can be synchronized across different browsers or incognito windows, provided they use the same URL and hash.
  8. How remote heads gossiping works

    main

    Remote heads gossiping allows a peer to learn about the state of other peers that are not directly connected to it, by routing information through intermediate nodes.

    The Mechanism

    1. Subscription: A peer sends a remote-subscription-changed message to a recipient to specify which storage_ids it wants to watch on its behalf.
    2. Detection: When a peer receives a sync message, it checks if the sender's storage_id is in any remote peer's subscription list and if the remote peer has permission to access the document (via sharePolicy).
    3. Notification: If checks pass, the local peer sends a remote-heads-changed message to the subscribing remote peer.
    4. Forwarding: When a peer receives a remote-heads-changed message, it verifies the timestamp is newer than the last known update for that storage_id/document combination before forwarding it.

    Example Scenario

    In a browser <-> sync server <-> browser setup, a browser tab subscribes to the sync server with the storage_id of another tab. The sync server then pushes remote-heads-changed messages to the first tab whenever the second tab's document heads change.

  9. How the Websocket Handshake works

    main

    Before synchronization begins, peers must complete a handshake to exchange identities and agree on a protocol version. The protocol distinguishes between the initiating peer (the client) and the receiving peer (the server).

    Handshake Steps

    1. Join: The initiating peer sends a join message containing its senderId and supportedProtocolVersions (currently only "1").
    2. Validation: The receiving peer validates the protocol version. If invalid, it sends an error message and terminates.
    3. Peer Exchange: The receiving peer stores the initiator's ID, emits a peer-candidate event, and responds with a peer message containing the targetId (the initiator's ID) and the selectedProtocolVersion.
    4. Completion: The initiating peer receives the peer message, stores the receiver's ID, emits a peer-candidate event, and begins the sync phase.

    Peer IDs vs. Storage IDs

    • Peer ID: An ephemeral ID tied to the lifetime of a specific process or browser tab.
    • Storage ID: An optional, persistent ID (e.g., tied to IndexedDB) advertised in join and peer messages. Multiple Peer IDs can share the same Storage ID. This allows peers to know whether to save and reload sync states for a specific persistent storage.
  10. Configure RepoContext for Solid apps

    main

    Instead of passing the repo instance manually to every hook, you can provide it via RepoContext. This allows hooks like useDocHandle and useDocument to access the repository automatically.

    Use useRepo() to retrieve the repository instance from the context.

    <RepoContext.Provider repo={Repo}>
      <App />
    </RepoContext.Provider>
    
    // Inside App:
    const repo = useRepo()
  11. Quickstart: React application with Automerge Repo

    main

    To set up a React application with Automerge Repo, follow these steps:

    1. Initialize Vite project:
    yarn create vite
    # Project name: hello-automerge-repo
    # Select a framework: React
    # Select a variant: TypeScript
    1. Install dependencies:
    cd hello-automerge-repo
    yarn
    yarn add @automerge/automerge @automerge/automerge-repo-react-hooks @automerge/automerge-repo-network-broadcastchannel @automerge/automerge-repo-storage-indexeddb vite-plugin-wasm
    1. Configure Vite for WASM: Update vite.config.ts to include vite-plugin-wasm to handle Automerge's WASM requirements.
    import { defineConfig } from "vite"
    import react from "@vitejs/plugin-react"
    import wasm from "vite-plugin-wasm"
    
    export default defineConfig({
      plugins: [wasm(), react()],
      worker: {
        format: "es",
        plugins: () => [wasm()],
      },
    })
    1. Initialize Repo in src/main.tsx: Create the Repo instance and provide it via RepoContext.Provider from @automerge/automerge-repo-react-hooks.

    2. Use documents in components: Use the useDocument hook to access and modify documents.

    import { useDocument } from "@automerge/automerge-repo-react-hooks"
    import { DocumentId } from "@automerge/automerge-repo"
    
    interface Doc {
      count: number
    }
    
    export default function App(props: { documentId: DocumentId }) {
      const [doc, changeDoc] = useDocument<Doc>(props.documentId)
    
      return (
        <button
          onClick={() => {
            changeDoc((d: any) => {
              d.count = (d.count || 0) + 1
            })
          }}
        >
          count is: {doc?.count ?? 0}
        </button>
      )
    }