Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

manymerge

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

manymerge - npm Package Compare versions

Comparing version 1.4.0 to 2.0.0

dist/hub.d.ts

31

dist/index.d.ts

@@ -1,24 +0,7 @@

/// <reference path="../@types/automerge/index.d.ts" />
import { Doc, Message } from "automerge";
export interface AsyncDocStore {
getDoc<T>(docId: string): Promise<Doc<T>>;
setDoc<T>(docId: string, doc: Doc<T>): Promise<Doc<T>>;
}
export declare class Connection {
private _docStore;
private _sendMsg;
private _ourClockMap;
private _theirClockMaps;
constructor(params: {
store: AsyncDocStore;
sendMsg: (peerId: string, msg: Message) => void;
});
addPeer(peerId: string): void;
docChanged(docId: string, doc: Doc<any>): Promise<void>;
receiveMsg(peerId: string, msg: Message): Promise<void | import("automerge").FreezeObject<unknown>>;
private sendMsg;
private updateOurClock;
private createMsg;
private syncDoc;
private maybeSyncDocWithPeer;
}
import Hub from "./hub";
import Peer from "./peer";
declare const _default: {
Hub: typeof Hub;
Peer: typeof Peer;
};
export default _default;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const automerge_1 = require("automerge");
const immutable_1 = require("immutable");
const invariant = require("invariant");
const lessOrEqual_1 = require("./lessOrEqual");
// Updates the vector clock for `docId` in `clockMap` (mapping from docId to vector clock)
// by merging in the new vector clock `clock`. Returns the updated `clockMap`, in which each node's
// sequence number has been set to the maximum for that node.
function clockUnion(clockMap, docId, clock) {
clock = clockMap.get(docId, immutable_1.Map()).mergeWith((x, y) => Math.max(x, y), clock);
return clockMap.set(docId, clock);
}
// Keeps track of the communication with one particular peer. Allows updates
// for many documents to be multiplexed over a single connection.
class Connection {
constructor(params) {
this._docStore = params.store;
this._sendMsg = params.sendMsg;
this._ourClockMap = immutable_1.Map();
this._theirClockMaps = immutable_1.Map();
}
// Manually adds a peer that we should talk to
addPeer(peerId) {
if (this._theirClockMaps.has(peerId)) {
return; // do nothing if we already have this peer
}
this._theirClockMaps = this._theirClockMaps.set(peerId, immutable_1.Map({}));
}
// manually call this when you want to change the document on the network.
async docChanged(docId, doc) {
const state = automerge_1.Frontend.getBackendState(doc);
const clock = state.getIn(["opSet", "clock"]);
if (!clock) {
throw new TypeError("This object cannot be used for network sync. " +
"Are you trying to sync a snapshot from the history?");
}
if (!lessOrEqual_1.default(this._ourClockMap.get(docId, immutable_1.Map()), clock)) {
throw new RangeError("Cannot pass an old state object to a connection");
}
await this.syncDoc(docId);
}
async receiveMsg(peerId, msg) {
if (!peerId || typeof peerId !== "string") {
throw new Error(`receiveMsg got a peerId that's not a string`);
}
invariant(this._theirClockMaps.keySeq().includes(peerId), `receivedMsg called with unknown peer '${peerId}'. Peers must be registered first with "conn.addPeer('${peerId}')"`);
if (msg.clock) {
this._theirClockMaps = this._theirClockMaps.set(peerId, clockUnion(this._theirClockMaps.get(peerId), msg.docId, immutable_1.fromJS(msg.clock)));
}
if (msg.changes) {
let doc = await this._docStore.getDoc(msg.docId);
if (!doc) {
doc = automerge_1.init({});
}
const newDoc = automerge_1.applyChanges(doc, msg.changes);
await this._docStore.setDoc(msg.docId, newDoc);
return this.syncDoc(msg.docId);
}
if (await this._docStore.getDoc(msg.docId)) {
this.syncDoc(msg.docId);
}
else if (!this._ourClockMap.has(msg.docId)) {
// If the remote node has data that we don't, immediately ask for it.
// TODO should we sometimes exercise restraint in what we ask for?
this.sendMsg(peerId, this.createMsg(msg.docId, immutable_1.Map()));
}
return this._docStore.getDoc(msg.docId);
}
sendMsg(peerId, msg) {
this.updateOurClock(msg.docId, msg.clock);
this._sendMsg(peerId, msg);
}
updateOurClock(docId, clock) {
this._ourClockMap = clockUnion(this._ourClockMap, docId, clock);
}
createMsg(docId, clock, changes) {
const msg = {
docId,
clock: clock.toJS()
};
if (changes)
msg.changes = changes;
return msg;
}
// Syncs document with everyone.
async syncDoc(docId) {
for (let peerId of this._theirClockMaps.keys()) {
await this.maybeSyncDocWithPeer(peerId, docId);
}
}
async maybeSyncDocWithPeer(theirPeerId, docId) {
const doc = await this._docStore.getDoc(docId);
if (!doc) {
throw new Error(`Couldn't find doc with id '${docId}'`);
}
const state = automerge_1.Frontend.getBackendState(doc);
const clock = state.getIn(["opSet", "clock"]);
const changes = automerge_1.Backend.getMissingChanges(state, this._theirClockMaps.get(theirPeerId).get(docId, immutable_1.Map()));
// if we have changes we need to sync, do so.
if (changes.length > 0) {
this._theirClockMaps = this._theirClockMaps.set(theirPeerId, clockUnion(this._theirClockMaps.get(theirPeerId), docId, clock));
this.sendMsg(theirPeerId, this.createMsg(docId, clock, changes));
return;
}
const ourClockIsOutOfSync = !clock.equals(this._ourClockMap.get(docId, immutable_1.Map()));
if (ourClockIsOutOfSync) {
// Note: updates ourClock AND sends a message.
this.sendMsg(theirPeerId, this.createMsg(docId, clock));
}
}
}
exports.Connection = Connection;
const hub_1 = require("./hub");
const peer_1 = require("./peer");
exports.default = {
Hub: hub_1.default,
Peer: peer_1.default
};
{
"name": "manymerge",
"version": "1.4.0",
"version": "2.0.0",
"main": "dist/index.js",

@@ -35,4 +35,5 @@ "author": "Evan Conrad",

"@types/events": "^3.0.0",
"automerge-clocks": "^1.0.0",
"invariant": "^2.2.4"
}
}
# ManyMerge
ManyMerge is a client for [Automerge](https://github.com/automerge/automerge) that, unlike the existing [Automerge.Connection](https://github.com/automerge/automerge/blob/master/src/connection.js), sends and receives changes from _multiple peers_ at once.
ManyMerge is a protocol for synchronizing Automerge documents. It's a replacement for `Automerge.Connection` that supports many-to-many and one-to-many relationships.
ManyMerge is network-opinionated, but lets you implement it yourself. It assumes you have the concept of "broadcasting", where everyone who has access to the document can be alerted of a message. It also assumes that you're keeping a unique id for each connection on your network (called the `peerId`).
## Install
```
npm install --save manymerge
```
## Usage
Manymerge comes with two different types of connections that work together: **Peers** and **Hubs**.
### Peers
A Peer is **a 1-1 relationship** that can talk to a Hub or another Peer. Your peer will need to create a `sendMsg` function that takes a ManyMerge `Message` and sends it to the network. Typically that looks like this:
```ts
import { Peer } from "manymerge";
function sendMsg(msg) {
MyNetwork.emit("to-server", msg);
}
const peer = new Peer(sendMsg);
```
When a peer wants to alert it's counterpart that it changed a document, it should call the `notify` function:
```ts
import Automerge from "automerge";
let myDoc = Automerge.from({ title: "cool doc" });
peer.notify(myDoc);
```
When a peer gets a message from the network, it should run `applyMessage`, which will return a new document
with any changes applied.
```ts
let myDoc = Automerge.from({ title: "cool doc" });
MyNetwork.on("from-server", msg => {
myDoc = peer.applyMessage(msg, myDoc);
});
```
### Hubs
Hubs are a **many-to-many (or 1-to-many) relationship** that can talk to many Peers or other Hubs. Unlike Peers, Hubs need the ability
to "broadcast" a message to everyone on the network (or at least as many people as possible).To save time, Hubs will also cache Peer's they've seen recently and directly communicate directly with them.
To set this up, create `broadcastMsg` and `sendMsgTo` functions:
```ts
import { Hub } from "manymerge";
function sendMsgTo(peerId, msg) {
MyNetwork.to(peerId).emit("msg", msg);
}
function broadcastMsg(msg) {
MyNetwork.on("some-channel").emit("msg", msg);
}
const hub = new Hub(sendMsgTo, broadcastMsg);
```
Then, hub works like a peer, it can notify others of documents:
```ts
// Tell folks about our doc
hub.notify(myDoc);
```
Unlike the peer, when it gets a message, it'll need to know the unique id of the connection sending it. It will use this later in the `sendMsgTo` function.
```ts
MyNetwork.on("msg", (from, msg) => {
myDoc = hub.applyMessage(from, msg, myDoc);
});
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc