@automerge/automerge-repo 1.0.0-alpha.0 → 1.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DocCollection.d.ts +2 -1
- package/dist/DocCollection.d.ts.map +1 -1
- package/dist/DocCollection.js +17 -8
- package/dist/DocHandle.d.ts +27 -7
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +47 -23
- package/dist/DocUrl.d.ts +3 -3
- package/dist/DocUrl.js +9 -9
- package/dist/EphemeralData.d.ts +8 -16
- package/dist/EphemeralData.d.ts.map +1 -1
- package/dist/EphemeralData.js +1 -28
- package/dist/Repo.d.ts +0 -2
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +18 -36
- package/dist/helpers/headsAreSame.d.ts +2 -2
- package/dist/helpers/headsAreSame.d.ts.map +1 -1
- package/dist/helpers/headsAreSame.js +1 -4
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +15 -13
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.d.ts +4 -13
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.d.ts +5 -4
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +39 -25
- package/dist/network/messages.d.ts +57 -0
- package/dist/network/messages.d.ts.map +1 -0
- package/dist/network/messages.js +21 -0
- package/dist/storage/StorageSubsystem.d.ts +2 -2
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +36 -6
- package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -2
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +19 -13
- package/dist/synchronizer/DocSynchronizer.d.ts +9 -3
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +145 -29
- package/dist/synchronizer/Synchronizer.d.ts +3 -4
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/dist/types.d.ts +1 -3
- package/dist/types.d.ts.map +1 -1
- package/fuzz/fuzz.ts +4 -4
- package/package.json +3 -3
- package/src/DocCollection.ts +19 -9
- package/src/DocHandle.ts +82 -37
- package/src/DocUrl.ts +9 -9
- package/src/EphemeralData.ts +6 -36
- package/src/Repo.ts +20 -52
- package/src/helpers/headsAreSame.ts +3 -5
- package/src/helpers/tests/network-adapter-tests.ts +18 -14
- package/src/index.ts +12 -2
- package/src/network/NetworkAdapter.ts +4 -20
- package/src/network/NetworkSubsystem.ts +61 -38
- package/src/network/messages.ts +123 -0
- package/src/storage/StorageSubsystem.ts +42 -6
- package/src/synchronizer/CollectionSynchronizer.ts +38 -19
- package/src/synchronizer/DocSynchronizer.ts +196 -38
- package/src/synchronizer/Synchronizer.ts +3 -8
- package/src/types.ts +4 -1
- package/test/CollectionSynchronizer.test.ts +6 -7
- package/test/DocHandle.test.ts +36 -22
- package/test/DocSynchronizer.test.ts +85 -9
- package/test/Repo.test.ts +279 -59
- package/test/StorageSubsystem.test.ts +9 -9
- package/test/helpers/DummyNetworkAdapter.ts +1 -1
- package/tsconfig.json +2 -1
- package/test/EphemeralData.test.ts +0 -44
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAc,MAAM,gBAAgB,CAAA;AAM9E;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA8HrE;AAID,KAAK,OAAO,GAAG,cAAc,GAAG,cAAc,EAAE,CAAA;AAEhD,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { Repo } from "../../index.js";
|
|
|
2
2
|
import { eventPromise, eventPromises } from "../eventPromise.js";
|
|
3
3
|
import { assert } from "chai";
|
|
4
4
|
import { describe, it } from "mocha";
|
|
5
|
+
import { pause } from "../pause.js";
|
|
5
6
|
/**
|
|
6
7
|
* Runs a series of tests against a set of three peers, each represented by one or more instantiated
|
|
7
8
|
* network adapters.
|
|
@@ -38,14 +39,14 @@ export function runAdapterTests(_setup, title) {
|
|
|
38
39
|
});
|
|
39
40
|
// Bob receives the change
|
|
40
41
|
await eventPromise(bobHandle, "change");
|
|
41
|
-
assert.equal((await bobHandle.doc())
|
|
42
|
+
assert.equal((await bobHandle.doc())?.foo, "bar");
|
|
42
43
|
// Bob changes the document
|
|
43
44
|
bobHandle.change(d => {
|
|
44
45
|
d.foo = "baz";
|
|
45
46
|
});
|
|
46
47
|
// Alice receives the change
|
|
47
48
|
await eventPromise(aliceHandle, "change");
|
|
48
|
-
assert.equal((await aliceHandle.doc())
|
|
49
|
+
assert.equal((await aliceHandle.doc())?.foo, "baz");
|
|
49
50
|
};
|
|
50
51
|
// Run the test in both directions, in case they're different types of adapters
|
|
51
52
|
{
|
|
@@ -80,32 +81,33 @@ export function runAdapterTests(_setup, title) {
|
|
|
80
81
|
});
|
|
81
82
|
// Bob and Charlie receive the change
|
|
82
83
|
await eventPromises([bobHandle, charlieHandle], "change");
|
|
83
|
-
assert.equal((await bobHandle.doc())
|
|
84
|
-
assert.equal((await charlieHandle.doc())
|
|
84
|
+
assert.equal((await bobHandle.doc())?.foo, "bar");
|
|
85
|
+
assert.equal((await charlieHandle.doc())?.foo, "bar");
|
|
85
86
|
// Charlie changes the document
|
|
86
87
|
charlieHandle.change(d => {
|
|
87
88
|
d.foo = "baz";
|
|
88
89
|
});
|
|
89
90
|
// Alice and Bob receive the change
|
|
90
91
|
await eventPromises([aliceHandle, bobHandle], "change");
|
|
91
|
-
assert.equal((await bobHandle.doc())
|
|
92
|
-
assert.equal((await charlieHandle.doc())
|
|
92
|
+
assert.equal((await bobHandle.doc())?.foo, "baz");
|
|
93
|
+
assert.equal((await charlieHandle.doc())?.foo, "baz");
|
|
93
94
|
teardown();
|
|
94
95
|
});
|
|
95
|
-
|
|
96
|
-
// because the network has cycles (see #92)
|
|
97
|
-
it.skip("can broadcast a message", async () => {
|
|
96
|
+
it("can broadcast a message", async () => {
|
|
98
97
|
const { adapters, teardown } = await setup();
|
|
99
98
|
const [a, b, c] = adapters;
|
|
100
99
|
const aliceRepo = new Repo({ network: a, peerId: alice });
|
|
101
100
|
const bobRepo = new Repo({ network: b, peerId: bob });
|
|
102
101
|
const charlieRepo = new Repo({ network: c, peerId: charlie });
|
|
103
102
|
await eventPromises([aliceRepo, bobRepo, charlieRepo].map(r => r.networkSubsystem), "peer");
|
|
104
|
-
const
|
|
103
|
+
const aliceHandle = aliceRepo.create();
|
|
104
|
+
const charlieHandle = charlieRepo.find(aliceHandle.url);
|
|
105
|
+
// pause to give charlie a chance to let alice know it wants the doc
|
|
106
|
+
await pause(100);
|
|
105
107
|
const alicePresenceData = { presence: "alice" };
|
|
106
|
-
|
|
107
|
-
const {
|
|
108
|
-
assert.deepStrictEqual(
|
|
108
|
+
aliceHandle.broadcast(alicePresenceData);
|
|
109
|
+
const { message } = await eventPromise(charlieHandle, "ephemeral-message");
|
|
110
|
+
assert.deepStrictEqual(message, alicePresenceData);
|
|
109
111
|
teardown();
|
|
110
112
|
});
|
|
111
113
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { DocCollection } from "./DocCollection.js";
|
|
|
2
2
|
export { DocHandle, HandleState } from "./DocHandle.js";
|
|
3
3
|
export type { DocHandleChangePayload } from "./DocHandle.js";
|
|
4
4
|
export { NetworkAdapter } from "./network/NetworkAdapter.js";
|
|
5
|
-
export type {
|
|
5
|
+
export type { OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, } from "./network/NetworkAdapter.js";
|
|
6
|
+
export type { Message, NetworkAdapterMessage, EphemeralMessage, SyncMessage, } from "./network/messages.js";
|
|
6
7
|
export { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
7
8
|
export { Repo, type SharePolicy } from "./Repo.js";
|
|
8
9
|
export { StorageAdapter, type StorageKey } from "./storage/StorageAdapter.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAA;AAMpC,YAAY,EACV,OAAO,EACP,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,IAAI,oBAAoB,GAC9C,MAAM,aAAa,CAAA;AACpB,cAAc,YAAY,CAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import EventEmitter from "eventemitter3";
|
|
2
|
-
import { PeerId
|
|
2
|
+
import { PeerId } from "../types.js";
|
|
3
|
+
import { Message } from "./messages.js";
|
|
3
4
|
export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
|
|
4
5
|
peerId?: PeerId;
|
|
5
6
|
abstract connect(url?: string): void;
|
|
6
|
-
abstract
|
|
7
|
+
abstract send(message: Message): void;
|
|
7
8
|
abstract join(): void;
|
|
8
9
|
abstract leave(): void;
|
|
9
10
|
}
|
|
@@ -12,7 +13,7 @@ export interface NetworkAdapterEvents {
|
|
|
12
13
|
close: () => void;
|
|
13
14
|
"peer-candidate": (payload: PeerCandidatePayload) => void;
|
|
14
15
|
"peer-disconnected": (payload: PeerDisconnectedPayload) => void;
|
|
15
|
-
message: (payload:
|
|
16
|
+
message: (payload: Message) => void;
|
|
16
17
|
}
|
|
17
18
|
export interface OpenPayload {
|
|
18
19
|
network: NetworkAdapter;
|
|
@@ -20,16 +21,6 @@ export interface OpenPayload {
|
|
|
20
21
|
export interface PeerCandidatePayload {
|
|
21
22
|
peerId: PeerId;
|
|
22
23
|
}
|
|
23
|
-
export interface MessagePayload {
|
|
24
|
-
targetId: PeerId;
|
|
25
|
-
channelId: ChannelId;
|
|
26
|
-
message: Uint8Array;
|
|
27
|
-
broadcast: boolean;
|
|
28
|
-
}
|
|
29
|
-
export interface InboundMessagePayload extends MessagePayload {
|
|
30
|
-
type?: string;
|
|
31
|
-
senderId: PeerId;
|
|
32
|
-
}
|
|
33
24
|
export interface PeerDisconnectedPayload {
|
|
34
25
|
peerId: PeerId;
|
|
35
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAEpC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,QAAQ,CAAC,IAAI,IAAI,IAAI;IAErB,QAAQ,CAAC,KAAK,IAAI,IAAI;CACvB;AAID,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACzD,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import EventEmitter from "eventemitter3";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { PeerId } from "../types.js";
|
|
3
|
+
import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
|
|
4
|
+
import { Message, MessageContents } from "./messages.js";
|
|
4
5
|
export declare class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
5
6
|
#private;
|
|
6
7
|
private adapters;
|
|
7
8
|
peerId: PeerId;
|
|
8
9
|
constructor(adapters: NetworkAdapter[], peerId?: PeerId);
|
|
9
10
|
addNetworkAdapter(networkAdapter: NetworkAdapter): void;
|
|
10
|
-
|
|
11
|
+
send(message: MessageContents): void;
|
|
11
12
|
join(): void;
|
|
12
13
|
leave(): void;
|
|
13
14
|
}
|
|
14
15
|
export interface NetworkSubsystemEvents {
|
|
15
16
|
peer: (payload: PeerPayload) => void;
|
|
16
17
|
"peer-disconnected": (payload: PeerDisconnectedPayload) => void;
|
|
17
|
-
message: (payload:
|
|
18
|
+
message: (payload: Message) => void;
|
|
18
19
|
}
|
|
19
20
|
export interface PeerPayload {
|
|
20
21
|
peerId: PeerId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,
|
|
1
|
+
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAE7E,OAAO,EAIL,OAAO,EACP,eAAe,EAChB,MAAM,eAAe,CAAA;AAUtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAStE,OAAO,CAAC,QAAQ;IACT,MAAM;gBADL,QAAQ,EAAE,cAAc,EAAE,EAC3B,MAAM,SAAiB;IAOhC,iBAAiB,CAAC,cAAc,EAAE,cAAc;IA0DhD,IAAI,CAAC,OAAO,EAAE,eAAe;IA2B7B,IAAI;IAKJ,KAAK;CAIN;AAQD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import EventEmitter from "eventemitter3";
|
|
2
|
+
import { isEphemeralMessage, isValidMessage, } from "./messages.js";
|
|
2
3
|
import debug from "debug";
|
|
4
|
+
const getEphemeralMessageSource = (message) => `${message.senderId}:${message.sessionId}`;
|
|
3
5
|
export class NetworkSubsystem extends EventEmitter {
|
|
4
6
|
adapters;
|
|
5
7
|
peerId;
|
|
6
8
|
#log;
|
|
7
9
|
#adaptersByPeer = {};
|
|
10
|
+
#count = 0;
|
|
11
|
+
#sessionId = Math.random().toString(36).slice(2);
|
|
12
|
+
#ephemeralSessionCounts = {};
|
|
8
13
|
constructor(adapters, peerId = randomPeerId()) {
|
|
9
14
|
super();
|
|
10
15
|
this.adapters = adapters;
|
|
@@ -29,18 +34,19 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
29
34
|
this.emit("peer-disconnected", { peerId });
|
|
30
35
|
});
|
|
31
36
|
networkAdapter.on("message", msg => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
37
|
+
if (!isValidMessage(msg)) {
|
|
38
|
+
this.#log(`invalid message: ${JSON.stringify(msg)}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.#log(`message from ${msg.senderId}`);
|
|
42
|
+
if (isEphemeralMessage(msg)) {
|
|
43
|
+
const source = getEphemeralMessageSource(msg);
|
|
44
|
+
if (this.#ephemeralSessionCounts[source] === undefined ||
|
|
45
|
+
msg.count > this.#ephemeralSessionCounts[source]) {
|
|
46
|
+
this.#ephemeralSessionCounts[source] = msg.count;
|
|
47
|
+
this.emit("message", msg);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
44
50
|
}
|
|
45
51
|
this.emit("message", msg);
|
|
46
52
|
});
|
|
@@ -54,21 +60,29 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
54
60
|
});
|
|
55
61
|
networkAdapter.join();
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
send(message) {
|
|
64
|
+
const peer = this.#adaptersByPeer[message.targetId];
|
|
65
|
+
if (!peer) {
|
|
66
|
+
this.#log(`Tried to send message but peer not found: ${message.targetId}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.#log(`Sending message to ${message.targetId}`);
|
|
70
|
+
if (isEphemeralMessage(message)) {
|
|
71
|
+
const outbound = "count" in message
|
|
72
|
+
? message
|
|
73
|
+
: {
|
|
74
|
+
...message,
|
|
75
|
+
count: ++this.#count,
|
|
76
|
+
sessionId: this.#sessionId,
|
|
77
|
+
senderId: this.peerId,
|
|
78
|
+
};
|
|
79
|
+
this.#log("Ephemeral message", outbound);
|
|
80
|
+
peer.send(outbound);
|
|
63
81
|
}
|
|
64
82
|
else {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
this.#log(`Sending message to ${peerId}`);
|
|
71
|
-
peer.sendMessage(peerId, channelId, message, false);
|
|
83
|
+
const outbound = { ...message, senderId: this.peerId };
|
|
84
|
+
this.#log("Sync message", outbound);
|
|
85
|
+
peer.send(outbound);
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
join() {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SessionId } from "../EphemeralData";
|
|
2
|
+
import { DocumentId, PeerId } from "../types";
|
|
3
|
+
export declare function isValidMessage(message: NetworkAdapterMessage): message is SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
|
|
4
|
+
export declare function isDocumentUnavailableMessage(message: NetworkAdapterMessage): message is DocumentUnavailableMessage;
|
|
5
|
+
export declare function isRequestMessage(message: NetworkAdapterMessage): message is RequestMessage;
|
|
6
|
+
export declare function isSyncMessage(message: NetworkAdapterMessage): message is SyncMessage;
|
|
7
|
+
export declare function isEphemeralMessage(message: NetworkAdapterMessage | MessageContents): message is EphemeralMessage | EphemeralMessageContents;
|
|
8
|
+
export interface SyncMessageEnvelope {
|
|
9
|
+
senderId: PeerId;
|
|
10
|
+
}
|
|
11
|
+
export interface SyncMessageContents {
|
|
12
|
+
type: "sync";
|
|
13
|
+
data: Uint8Array;
|
|
14
|
+
targetId: PeerId;
|
|
15
|
+
documentId: DocumentId;
|
|
16
|
+
}
|
|
17
|
+
export type SyncMessage = SyncMessageEnvelope & SyncMessageContents;
|
|
18
|
+
export interface EphemeralMessageEnvelope {
|
|
19
|
+
senderId: PeerId;
|
|
20
|
+
count: number;
|
|
21
|
+
sessionId: SessionId;
|
|
22
|
+
}
|
|
23
|
+
export interface EphemeralMessageContents {
|
|
24
|
+
type: "ephemeral";
|
|
25
|
+
targetId: PeerId;
|
|
26
|
+
documentId: DocumentId;
|
|
27
|
+
data: Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
export type EphemeralMessage = EphemeralMessageEnvelope & EphemeralMessageContents;
|
|
30
|
+
export interface DocumentUnavailableMessageContents {
|
|
31
|
+
type: "doc-unavailable";
|
|
32
|
+
documentId: DocumentId;
|
|
33
|
+
targetId: PeerId;
|
|
34
|
+
}
|
|
35
|
+
export type DocumentUnavailableMessage = SyncMessageEnvelope & DocumentUnavailableMessageContents;
|
|
36
|
+
export interface RequestMessageContents {
|
|
37
|
+
type: "request";
|
|
38
|
+
data: Uint8Array;
|
|
39
|
+
targetId: PeerId;
|
|
40
|
+
documentId: DocumentId;
|
|
41
|
+
}
|
|
42
|
+
export type RequestMessage = SyncMessageEnvelope & RequestMessageContents;
|
|
43
|
+
export type MessageContents = SyncMessageContents | EphemeralMessageContents | RequestMessageContents | DocumentUnavailableMessageContents;
|
|
44
|
+
export type Message = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
|
|
45
|
+
export type SynchronizerMessage = SyncMessage | RequestMessage | DocumentUnavailableMessage | EphemeralMessage;
|
|
46
|
+
type ArriveMessage = {
|
|
47
|
+
senderId: PeerId;
|
|
48
|
+
type: "arrive";
|
|
49
|
+
};
|
|
50
|
+
type WelcomeMessage = {
|
|
51
|
+
senderId: PeerId;
|
|
52
|
+
targetId: PeerId;
|
|
53
|
+
type: "welcome";
|
|
54
|
+
};
|
|
55
|
+
export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAE7C,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IACN,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAU7B;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,0BAA0B,CAEvC;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,cAAc,CAE3B;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,WAAW,CAExB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,qBAAqB,GAAG,eAAe,GAC/C,OAAO,IAAI,gBAAgB,GAAG,wBAAwB,CAExD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,mBAAmB,CAAA;AAEnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,GACrD,wBAAwB,CAAA;AAE1B,MAAM,WAAW,kCAAkC;IACjD,IAAI,EAAE,iBAAiB,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAC1D,kCAAkC,CAAA;AAEpC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,mBAAmB,GAAG,sBAAsB,CAAA;AAEzE,MAAM,MAAM,eAAe,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,sBAAsB,GACtB,kCAAkC,CAAA;AAEtC,MAAM,MAAM,OAAO,GACf,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAAA;AAE9B,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,cAAc,GACd,0BAA0B,GAC1B,gBAAgB,CAAA;AAEpB,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,QAAQ,CAAA;CACf,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function isValidMessage(message) {
|
|
2
|
+
return (typeof message === "object" &&
|
|
3
|
+
typeof message.type === "string" &&
|
|
4
|
+
typeof message.senderId === "string" &&
|
|
5
|
+
(isSyncMessage(message) ||
|
|
6
|
+
isEphemeralMessage(message) ||
|
|
7
|
+
isRequestMessage(message) ||
|
|
8
|
+
isDocumentUnavailableMessage(message)));
|
|
9
|
+
}
|
|
10
|
+
export function isDocumentUnavailableMessage(message) {
|
|
11
|
+
return message.type === "doc-unavailable";
|
|
12
|
+
}
|
|
13
|
+
export function isRequestMessage(message) {
|
|
14
|
+
return message.type === "request";
|
|
15
|
+
}
|
|
16
|
+
export function isSyncMessage(message) {
|
|
17
|
+
return message.type === "sync";
|
|
18
|
+
}
|
|
19
|
+
export function isEphemeralMessage(message) {
|
|
20
|
+
return message.type === "ephemeral";
|
|
21
|
+
}
|
|
@@ -5,8 +5,8 @@ export type ChunkType = "snapshot" | "incremental";
|
|
|
5
5
|
export declare class StorageSubsystem {
|
|
6
6
|
#private;
|
|
7
7
|
constructor(storageAdapter: StorageAdapter);
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
loadDoc(documentId: DocumentId): Promise<A.Doc<unknown> | null>;
|
|
9
|
+
saveDoc(documentId: DocumentId, doc: A.Doc<unknown>): Promise<void>;
|
|
10
10
|
remove(documentId: DocumentId): Promise<void>;
|
|
11
11
|
}
|
|
12
12
|
//# sourceMappingURL=StorageSubsystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAA;AAEhE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAA;AAEhE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAa7C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAelD,qBAAa,gBAAgB;;gBAMf,cAAc,EAAE,cAAc;IAqDpC,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IA0B/D,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAanE,MAAM,CAAC,UAAU,EAAE,UAAU;CAgCpC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge";
|
|
2
2
|
import * as sha256 from "fast-sha256";
|
|
3
3
|
import { mergeArrays } from "../helpers/mergeArrays.js";
|
|
4
|
+
import debug from "debug";
|
|
5
|
+
import { headsAreSame } from "../helpers/headsAreSame.js";
|
|
4
6
|
function keyHash(binary) {
|
|
5
7
|
const hash = sha256.hash(binary);
|
|
6
8
|
const hashArray = Array.from(new Uint8Array(hash)); // convert buffer to byte array
|
|
@@ -15,13 +17,16 @@ function headsHash(heads) {
|
|
|
15
17
|
export class StorageSubsystem {
|
|
16
18
|
#storageAdapter;
|
|
17
19
|
#chunkInfos = new Map();
|
|
20
|
+
#storedHeads = new Map();
|
|
21
|
+
#log = debug(`automerge-repo:storage-subsystem`);
|
|
18
22
|
constructor(storageAdapter) {
|
|
19
23
|
this.#storageAdapter = storageAdapter;
|
|
20
24
|
}
|
|
21
25
|
async #saveIncremental(documentId, doc) {
|
|
22
|
-
const binary = A.
|
|
26
|
+
const binary = A.saveSince(doc, this.#storedHeads.get(documentId) ?? []);
|
|
23
27
|
if (binary && binary.length > 0) {
|
|
24
28
|
const key = [documentId, "incremental", keyHash(binary)];
|
|
29
|
+
this.#log(`Saving incremental ${key} for document ${documentId}`);
|
|
25
30
|
await this.#storageAdapter.save(key, binary);
|
|
26
31
|
if (!this.#chunkInfos.has(documentId)) {
|
|
27
32
|
this.#chunkInfos.set(documentId, []);
|
|
@@ -31,6 +36,7 @@ export class StorageSubsystem {
|
|
|
31
36
|
type: "incremental",
|
|
32
37
|
size: binary.length,
|
|
33
38
|
});
|
|
39
|
+
this.#storedHeads.set(documentId, A.getHeads(doc));
|
|
34
40
|
}
|
|
35
41
|
else {
|
|
36
42
|
return Promise.resolve();
|
|
@@ -38,8 +44,11 @@ export class StorageSubsystem {
|
|
|
38
44
|
}
|
|
39
45
|
async #saveTotal(documentId, doc, sourceChunks) {
|
|
40
46
|
const binary = A.save(doc);
|
|
41
|
-
const
|
|
42
|
-
const
|
|
47
|
+
const snapshotHash = headsHash(A.getHeads(doc));
|
|
48
|
+
const key = [documentId, "snapshot", snapshotHash];
|
|
49
|
+
const oldKeys = new Set(sourceChunks.map(c => c.key).filter(k => k[2] !== snapshotHash));
|
|
50
|
+
this.#log(`Saving snapshot ${key} for document ${documentId}`);
|
|
51
|
+
this.#log(`deleting old chunks ${Array.from(oldKeys)}`);
|
|
43
52
|
await this.#storageAdapter.save(key, binary);
|
|
44
53
|
for (const key of oldKeys) {
|
|
45
54
|
await this.#storageAdapter.remove(key);
|
|
@@ -48,7 +57,7 @@ export class StorageSubsystem {
|
|
|
48
57
|
newChunkInfos.push({ key, type: "snapshot", size: binary.length });
|
|
49
58
|
this.#chunkInfos.set(documentId, newChunkInfos);
|
|
50
59
|
}
|
|
51
|
-
async
|
|
60
|
+
async loadDoc(documentId) {
|
|
52
61
|
const loaded = await this.#storageAdapter.loadRange([documentId]);
|
|
53
62
|
const binaries = [];
|
|
54
63
|
const chunkInfos = [];
|
|
@@ -65,9 +74,18 @@ export class StorageSubsystem {
|
|
|
65
74
|
binaries.push(chunk.data);
|
|
66
75
|
}
|
|
67
76
|
this.#chunkInfos.set(documentId, chunkInfos);
|
|
68
|
-
|
|
77
|
+
const binary = mergeArrays(binaries);
|
|
78
|
+
if (binary.length === 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const newDoc = A.loadIncremental(A.init(), binary);
|
|
82
|
+
this.#storedHeads.set(documentId, A.getHeads(newDoc));
|
|
83
|
+
return newDoc;
|
|
69
84
|
}
|
|
70
|
-
async
|
|
85
|
+
async saveDoc(documentId, doc) {
|
|
86
|
+
if (!this.#shouldSave(documentId, doc)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
71
89
|
let sourceChunks = this.#chunkInfos.get(documentId) ?? [];
|
|
72
90
|
if (this.#shouldCompact(sourceChunks)) {
|
|
73
91
|
this.#saveTotal(documentId, doc, sourceChunks);
|
|
@@ -75,11 +93,23 @@ export class StorageSubsystem {
|
|
|
75
93
|
else {
|
|
76
94
|
this.#saveIncremental(documentId, doc);
|
|
77
95
|
}
|
|
96
|
+
this.#storedHeads.set(documentId, A.getHeads(doc));
|
|
78
97
|
}
|
|
79
98
|
async remove(documentId) {
|
|
80
99
|
this.#storageAdapter.remove([documentId, "snapshot"]);
|
|
81
100
|
this.#storageAdapter.removeRange([documentId, "incremental"]);
|
|
82
101
|
}
|
|
102
|
+
#shouldSave(documentId, doc) {
|
|
103
|
+
const oldHeads = this.#storedHeads.get(documentId);
|
|
104
|
+
if (!oldHeads) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const newHeads = A.getHeads(doc);
|
|
108
|
+
if (headsAreSame(newHeads, oldHeads)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
83
113
|
#shouldCompact(sourceChunks) {
|
|
84
114
|
// compact if the incremental size is greater than the snapshot size
|
|
85
115
|
let snapshotSize = 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DocCollection } from "../DocCollection.js";
|
|
2
|
-
import {
|
|
2
|
+
import { PeerId, DocumentId } from "../types.js";
|
|
3
3
|
import { Synchronizer } from "./Synchronizer.js";
|
|
4
|
+
import { SynchronizerMessage } from "../network/messages.js";
|
|
4
5
|
/** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
|
|
5
6
|
export declare class CollectionSynchronizer extends Synchronizer {
|
|
6
7
|
#private;
|
|
@@ -10,7 +11,7 @@ export declare class CollectionSynchronizer extends Synchronizer {
|
|
|
10
11
|
* When we receive a sync message for a document we haven't got in memory, we
|
|
11
12
|
* register it with the repo and start synchronizing
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
receiveMessage(message: SynchronizerMessage): Promise<void>;
|
|
14
15
|
/**
|
|
15
16
|
* Starts synchronizing the given document with all peers that we share it generously with.
|
|
16
17
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAOnD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAOnD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAGL,mBAAmB,EAEpB,MAAM,wBAAwB,CAAA;AAG/B,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAiCvC;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,mBAAmB;IAyBjD;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAYlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;CAQ1B"}
|
|
@@ -10,6 +10,8 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
10
10
|
#peers = new Set();
|
|
11
11
|
/** A map of documentIds to their synchronizers */
|
|
12
12
|
#docSynchronizers = {};
|
|
13
|
+
/** Used to determine if the document is know to the Collection and a synchronizer exists or is being set up */
|
|
14
|
+
#docSetUp = {};
|
|
13
15
|
constructor(repo) {
|
|
14
16
|
super();
|
|
15
17
|
this.repo = repo;
|
|
@@ -44,29 +46,30 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
44
46
|
* When we receive a sync message for a document we haven't got in memory, we
|
|
45
47
|
* register it with the repo and start synchronizing
|
|
46
48
|
*/
|
|
47
|
-
async
|
|
48
|
-
log(`onSyncMessage: ${
|
|
49
|
-
const documentId =
|
|
49
|
+
async receiveMessage(message) {
|
|
50
|
+
log(`onSyncMessage: ${message.senderId}, ${message.documentId}, ${"data" in message ? message.data.byteLength + "bytes" : ""}`);
|
|
51
|
+
const documentId = message.documentId;
|
|
50
52
|
if (!documentId) {
|
|
51
53
|
throw new Error("received a message with an invalid documentId");
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
this.#docSetUp[documentId] = true;
|
|
56
|
+
const docSynchronizer = this.#fetchDocSynchronizer(documentId);
|
|
57
|
+
docSynchronizer.receiveMessage(message);
|
|
55
58
|
// Initiate sync with any new peers
|
|
56
59
|
const peers = await this.#documentGenerousPeers(documentId);
|
|
57
|
-
peers
|
|
58
|
-
.filter(peerId => !docSynchronizer.hasPeer(peerId))
|
|
59
|
-
.forEach(peerId => docSynchronizer.beginSync(peerId));
|
|
60
|
+
docSynchronizer.beginSync(peers.filter(peerId => !docSynchronizer.hasPeer(peerId)));
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Starts synchronizing the given document with all peers that we share it generously with.
|
|
63
64
|
*/
|
|
64
65
|
addDocument(documentId) {
|
|
66
|
+
// HACK: this is a hack to prevent us from adding the same document twice
|
|
67
|
+
if (this.#docSetUp[documentId]) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
65
70
|
const docSynchronizer = this.#fetchDocSynchronizer(documentId);
|
|
66
71
|
void this.#documentGenerousPeers(documentId).then(peers => {
|
|
67
|
-
|
|
68
|
-
docSynchronizer.beginSync(peerId);
|
|
69
|
-
});
|
|
72
|
+
docSynchronizer.beginSync(peers);
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
// TODO: implement this
|
|
@@ -76,12 +79,15 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
76
79
|
/** Adds a peer and maybe starts synchronizing with them */
|
|
77
80
|
addPeer(peerId) {
|
|
78
81
|
log(`adding ${peerId} & synchronizing with them`);
|
|
82
|
+
if (this.#peers.has(peerId)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
79
85
|
this.#peers.add(peerId);
|
|
80
86
|
for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
|
|
81
87
|
const { documentId } = docSynchronizer;
|
|
82
|
-
|
|
88
|
+
this.repo.sharePolicy(peerId, documentId).then(okToShare => {
|
|
83
89
|
if (okToShare)
|
|
84
|
-
docSynchronizer.beginSync(peerId);
|
|
90
|
+
docSynchronizer.beginSync([peerId]);
|
|
85
91
|
});
|
|
86
92
|
}
|
|
87
93
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DocHandle } from "../DocHandle.js";
|
|
2
|
-
import {
|
|
2
|
+
import { PeerId } from "../types.js";
|
|
3
3
|
import { Synchronizer } from "./Synchronizer.js";
|
|
4
|
+
import { EphemeralMessage, RequestMessage, SynchronizerMessage, SyncMessage } from "../network/messages.js";
|
|
5
|
+
type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants";
|
|
4
6
|
/**
|
|
5
7
|
* DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
|
|
6
8
|
* to bring it inline with all other peers' versions.
|
|
@@ -9,10 +11,14 @@ export declare class DocSynchronizer extends Synchronizer {
|
|
|
9
11
|
#private;
|
|
10
12
|
private handle;
|
|
11
13
|
constructor(handle: DocHandle<any>);
|
|
14
|
+
get peerStates(): Record<PeerId, PeerDocumentStatus>;
|
|
12
15
|
get documentId(): import("../types.js").DocumentId;
|
|
13
16
|
hasPeer(peerId: PeerId): boolean;
|
|
14
|
-
beginSync(
|
|
17
|
+
beginSync(peerIds: PeerId[]): void;
|
|
15
18
|
endSync(peerId: PeerId): void;
|
|
16
|
-
|
|
19
|
+
receiveMessage(message: SynchronizerMessage): void;
|
|
20
|
+
receiveEphemeralMessage(message: EphemeralMessage): void;
|
|
21
|
+
receiveSyncMessage(message: SyncMessage | RequestMessage): void;
|
|
17
22
|
}
|
|
23
|
+
export {};
|
|
18
24
|
//# sourceMappingURL=DocSynchronizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EACL,gBAAgB,EAIhB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAE/B,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAGrE;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAiBnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC;IAoB1C,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAiHD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA8B3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,mBAAmB;IAkB3C,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA2EzD"}
|