@automerge/automerge-repo 1.1.0 → 1.1.1
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/README.md +2 -2
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +4 -5
- package/dist/Repo.d.ts +4 -4
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +2 -4
- package/dist/helpers/tests/network-adapter-tests.d.ts +2 -2
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +16 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.d.ts +4 -34
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.js +1 -0
- package/dist/network/NetworkAdapterInterface.d.ts +61 -0
- package/dist/network/NetworkAdapterInterface.d.ts.map +1 -0
- package/dist/network/NetworkAdapterInterface.js +2 -0
- package/dist/network/NetworkSubsystem.d.ts +3 -3
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +4 -2
- package/dist/storage/StorageAdapter.d.ts +3 -1
- package/dist/storage/StorageAdapter.d.ts.map +1 -1
- package/dist/storage/StorageAdapter.js +1 -0
- package/dist/storage/StorageAdapterInterface.d.ts +30 -0
- package/dist/storage/StorageAdapterInterface.d.ts.map +1 -0
- package/dist/storage/StorageAdapterInterface.js +1 -0
- package/dist/storage/StorageSubsystem.d.ts +2 -2
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +2 -2
- package/package.json +2 -2
- package/src/DocHandle.ts +7 -8
- package/src/Repo.ts +11 -15
- package/src/helpers/tests/network-adapter-tests.ts +30 -4
- package/src/index.ts +3 -1
- package/src/network/NetworkAdapter.ts +7 -45
- package/src/network/NetworkAdapterInterface.ts +77 -0
- package/src/network/NetworkSubsystem.ts +13 -11
- package/src/storage/StorageAdapter.ts +3 -1
- package/src/storage/StorageAdapterInterface.ts +34 -0
- package/src/storage/StorageSubsystem.ts +3 -3
- package/src/synchronizer/DocSynchronizer.ts +3 -3
- package/test/DocHandle.test.ts +24 -1
- package/test/Repo.test.ts +32 -1
- package/test/helpers/DummyNetworkAdapter.ts +37 -5
package/README.md
CHANGED
|
@@ -38,8 +38,8 @@ This library provides two main components: the `Repo` itself, and the `DocHandle
|
|
|
38
38
|
|
|
39
39
|
A `Repo` exposes these methods:
|
|
40
40
|
|
|
41
|
-
- `create<T>()`
|
|
42
|
-
Creates a new
|
|
41
|
+
- `create<T>(initialValue: T?)`
|
|
42
|
+
Creates a new `Automerge.Doc` and returns a `DocHandle` for it. Accepts an optional initial value for the document. Produces an empty document (potentially violating the type!) otherwise.
|
|
43
43
|
- `find<T>(docId: DocumentId)`
|
|
44
44
|
Looks up a given document either on the local machine or (if necessary) over any configured
|
|
45
45
|
networks.
|
package/dist/DocHandle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EASL,UAAU,EAEX,MAAM,QAAQ,CAAA;AAMf,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;;;;;;KAWK;AACL,qBAAa,SAAS,CAAC,CAAC,CAAE,EAAE;AAC1B,SAAQ,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;;IAmB/B,UAAU,EAAE,UAAU;IAX/B;;;;OAIG;IACH,IAAI,GAAG,IAAI,YAAY,CAEtB;IAED,cAAc;gBAEL,UAAU,EAAE,UAAU,EAC7B,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM;
|
|
1
|
+
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
|
package/dist/DocHandle.js
CHANGED
|
@@ -201,11 +201,10 @@ export class DocHandle//
|
|
|
201
201
|
}
|
|
202
202
|
/** Returns a promise that resolves when the docHandle is in one of the given states */
|
|
203
203
|
#statePromise(awaitStates) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
})));
|
|
204
|
+
const awaitStatesArray = Array.isArray(awaitStates) ? awaitStates : [awaitStates];
|
|
205
|
+
return waitFor(this.#machine, s => awaitStatesArray.some((state) => s.matches(state)),
|
|
206
|
+
// use a longer delay here so as not to race with other delays
|
|
207
|
+
{ timeout: this.#timeoutDelay * 2 });
|
|
209
208
|
}
|
|
210
209
|
// PUBLIC
|
|
211
210
|
/**
|
package/dist/Repo.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { DocHandle } from "./DocHandle.js";
|
|
3
|
-
import {
|
|
3
|
+
import { NetworkAdapterInterface, type PeerMetadata } from "./network/NetworkAdapterInterface.js";
|
|
4
4
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
5
|
-
import {
|
|
5
|
+
import { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js";
|
|
6
6
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js";
|
|
7
7
|
import { StorageId } from "./storage/types.js";
|
|
8
8
|
import type { AnyDocumentId, DocumentId, PeerId } from "./types.js";
|
|
@@ -90,9 +90,9 @@ export interface RepoConfig {
|
|
|
90
90
|
* Sync state is only persisted for non-ephemeral peers */
|
|
91
91
|
isEphemeral?: boolean;
|
|
92
92
|
/** A storage adapter can be provided, or not */
|
|
93
|
-
storage?:
|
|
93
|
+
storage?: StorageAdapterInterface;
|
|
94
94
|
/** One or more network adapters must be provided */
|
|
95
|
-
network:
|
|
95
|
+
network: NetworkAdapterInterface[];
|
|
96
96
|
/**
|
|
97
97
|
* Normal peers typically share generously with everyone (meaning we sync all our documents with
|
|
98
98
|
* all peers). A server only syncs documents that a peer explicitly requests by ID.
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,OAAO,EAAE,SAAS,EAAiC,MAAM,gBAAgB,CAAA;AAIzE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
|
package/dist/Repo.js
CHANGED
|
@@ -114,12 +114,10 @@ export class Repo extends EventEmitter {
|
|
|
114
114
|
this.storageSubsystem = storageSubsystem;
|
|
115
115
|
// NETWORK
|
|
116
116
|
// The network subsystem deals with sending and receiving messages to and from peers.
|
|
117
|
-
const myPeerMetadata =
|
|
118
|
-
// eslint-disable-next-line no-async-promise-executor -- TODO: fix
|
|
119
|
-
async (resolve) => resolve({
|
|
117
|
+
const myPeerMetadata = (async () => ({
|
|
120
118
|
storageId: await storageSubsystem?.id(),
|
|
121
119
|
isEphemeral,
|
|
122
|
-
}));
|
|
120
|
+
}))();
|
|
123
121
|
const networkSubsystem = new NetworkSubsystem(network, peerId, myPeerMetadata);
|
|
124
122
|
this.networkSubsystem = networkSubsystem;
|
|
125
123
|
// When we get a new peer, register it with the synchronizer
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { NetworkAdapterInterface } from "../../network/NetworkAdapterInterface.js";
|
|
2
2
|
/**
|
|
3
3
|
* Runs a series of tests against a set of three peers, each represented by one or more instantiated
|
|
4
4
|
* network adapters.
|
|
@@ -12,7 +12,7 @@ import { type NetworkAdapter } from "../../index.js";
|
|
|
12
12
|
* to clean up any resources that were created during the test.
|
|
13
13
|
*/
|
|
14
14
|
export declare function runAdapterTests(_setup: SetupFn, title?: string): void;
|
|
15
|
-
type Network =
|
|
15
|
+
type Network = NetworkAdapterInterface | NetworkAdapterInterface[];
|
|
16
16
|
export type SetupFn = () => Promise<{
|
|
17
17
|
adapters: [Network, Network, Network];
|
|
18
18
|
teardown?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAIvF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAuJrE;AAID,KAAK,OAAO,GAAG,uBAAuB,GAAG,uBAAuB,EAAE,CAAA;AAElE,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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
|
-
import { describe, it } from "vitest";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { Repo } from "../../index.js";
|
|
4
4
|
import { eventPromise, eventPromises } from "../eventPromise.js";
|
|
5
5
|
import { pause } from "../pause.js";
|
|
@@ -110,6 +110,21 @@ export function runAdapterTests(_setup, title) {
|
|
|
110
110
|
assert.deepStrictEqual(message, alicePresenceData);
|
|
111
111
|
teardown();
|
|
112
112
|
});
|
|
113
|
+
it("emits a peer-candidate event with proper peer metadata when a peer connects", async () => {
|
|
114
|
+
const { adapters, teardown } = await setup();
|
|
115
|
+
const a = adapters[0][0];
|
|
116
|
+
const b = adapters[1][0];
|
|
117
|
+
const bPromise = eventPromise(b, "peer-candidate");
|
|
118
|
+
const aPeerMetadata = { storageId: "a" };
|
|
119
|
+
b.connect("b", { storageId: "b" });
|
|
120
|
+
a.connect("a", aPeerMetadata);
|
|
121
|
+
const peerCandidate = await bPromise;
|
|
122
|
+
expect(peerCandidate).toMatchObject({
|
|
123
|
+
peerId: "a",
|
|
124
|
+
peerMetadata: aPeerMetadata,
|
|
125
|
+
});
|
|
126
|
+
teardown();
|
|
127
|
+
});
|
|
113
128
|
});
|
|
114
129
|
}
|
|
115
130
|
const NO_OP = () => { };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,13 +29,15 @@ export { DocHandle } from "./DocHandle.js";
|
|
|
29
29
|
export { isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "./AutomergeUrl.js";
|
|
30
30
|
export { Repo } from "./Repo.js";
|
|
31
31
|
export { NetworkAdapter } from "./network/NetworkAdapter.js";
|
|
32
|
+
export type { NetworkAdapterInterface } from "./network/NetworkAdapterInterface.js";
|
|
32
33
|
export { isRepoMessage } from "./network/messages.js";
|
|
33
34
|
export { StorageAdapter } from "./storage/StorageAdapter.js";
|
|
35
|
+
export type { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js";
|
|
34
36
|
/** @hidden **/
|
|
35
37
|
export * as cbor from "./helpers/cbor.js";
|
|
36
38
|
export type { DocHandleChangePayload, DocHandleDeletePayload, DocHandleEncodedChangePayload, DocHandleEphemeralMessagePayload, DocHandleRemoteHeadsPayload, DocHandleEvents, DocHandleOptions, DocHandleOutboundEphemeralMessagePayload, HandleState, } from "./DocHandle.js";
|
|
37
39
|
export type { DeleteDocumentPayload, DocumentPayload, RepoConfig, RepoEvents, SharePolicy, } from "./Repo.js";
|
|
38
|
-
export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, PeerMetadata, } from "./network/
|
|
40
|
+
export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, PeerMetadata, } from "./network/NetworkAdapterInterface.js";
|
|
39
41
|
export type { DocumentUnavailableMessage, EphemeralMessage, Message, RepoMessage, RequestMessage, SyncMessage, } from "./network/messages.js";
|
|
40
42
|
export type { Chunk, ChunkInfo, ChunkType, StorageKey, StorageId, } from "./storage/types.js";
|
|
41
43
|
export * from "./types.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;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAEnF,eAAe;AACf,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA;AAIzC,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,wCAAwC,EACxC,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,uBAAuB,EACvB,YAAY,GACb,MAAM,sCAAsC,CAAA;AAE7C,YAAY,EACV,0BAA0B,EAC1B,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,YAAY,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAA;AAE3B,cAAc,YAAY,CAAA"}
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
|
+
import { NetworkAdapterEvents, PeerMetadata } from "../index.js";
|
|
2
3
|
import { PeerId } from "../types.js";
|
|
3
4
|
import { Message } from "./messages.js";
|
|
4
|
-
import {
|
|
5
|
-
/**
|
|
6
|
-
* Describes a peer intent to the system
|
|
7
|
-
* storageId: the key for syncState to decide what the other peer already has
|
|
8
|
-
* isEphemeral: to decide if we bother recording this peer's sync state
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
export interface PeerMetadata {
|
|
12
|
-
storageId?: StorageId;
|
|
13
|
-
isEphemeral?: boolean;
|
|
14
|
-
}
|
|
5
|
+
import { NetworkAdapterInterface } from "./NetworkAdapterInterface.js";
|
|
15
6
|
/** An interface representing some way to connect to other peers
|
|
7
|
+
* @deprecated use {@link NetworkAdapterInterface}
|
|
16
8
|
*
|
|
17
9
|
* @remarks
|
|
18
10
|
* The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
|
|
19
11
|
* Because the network may take some time to be ready the {@link Repo} will wait
|
|
20
12
|
* until the adapter emits a `ready` event before it starts trying to use it
|
|
21
13
|
*/
|
|
22
|
-
export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
|
|
14
|
+
export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> implements NetworkAdapterInterface {
|
|
23
15
|
peerId?: PeerId;
|
|
24
16
|
peerMetadata?: PeerMetadata;
|
|
25
17
|
/** Called by the {@link Repo} to start the connection process
|
|
@@ -36,26 +28,4 @@ export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapter
|
|
|
36
28
|
/** Called by the {@link Repo} to disconnect from the network */
|
|
37
29
|
abstract disconnect(): void;
|
|
38
30
|
}
|
|
39
|
-
export interface NetworkAdapterEvents {
|
|
40
|
-
/** Emitted when the network is ready to be used */
|
|
41
|
-
ready: (payload: OpenPayload) => void;
|
|
42
|
-
/** Emitted when the network is closed */
|
|
43
|
-
close: () => void;
|
|
44
|
-
/** Emitted when the network adapter learns about a new peer */
|
|
45
|
-
"peer-candidate": (payload: PeerCandidatePayload) => void;
|
|
46
|
-
/** Emitted when the network adapter learns that a peer has disconnected */
|
|
47
|
-
"peer-disconnected": (payload: PeerDisconnectedPayload) => void;
|
|
48
|
-
/** Emitted when the network adapter receives a message from a peer */
|
|
49
|
-
message: (payload: Message) => void;
|
|
50
|
-
}
|
|
51
|
-
export interface OpenPayload {
|
|
52
|
-
network: NetworkAdapter;
|
|
53
|
-
}
|
|
54
|
-
export interface PeerCandidatePayload {
|
|
55
|
-
peerId: PeerId;
|
|
56
|
-
peerMetadata: PeerMetadata;
|
|
57
|
-
}
|
|
58
|
-
export interface PeerDisconnectedPayload {
|
|
59
|
-
peerId: PeerId;
|
|
60
|
-
}
|
|
61
31
|
//# sourceMappingURL=NetworkAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE;;;;;;;GAOG;AACH,8BAAsB,cACpB,SAAQ,YAAY,CAAC,oBAAoB,CACzC,YAAW,uBAAuB;IAElC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,YAAY,CAAA;IAE3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI;IAEnE;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,IAAI,IAAI;CAC5B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* c8 ignore start */
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
3
|
/** An interface representing some way to connect to other peers
|
|
4
|
+
* @deprecated use {@link NetworkAdapterInterface}
|
|
4
5
|
*
|
|
5
6
|
* @remarks
|
|
6
7
|
* The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { EventEmitter } from "eventemitter3";
|
|
2
|
+
import { PeerId } from "../types.js";
|
|
3
|
+
import { Message } from "./messages.js";
|
|
4
|
+
import { StorageId } from "../storage/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Describes a peer intent to the system
|
|
7
|
+
* storageId: the key for syncState to decide what the other peer already has
|
|
8
|
+
* isEphemeral: to decide if we bother recording this peer's sync state
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export interface PeerMetadata {
|
|
12
|
+
storageId?: StorageId;
|
|
13
|
+
isEphemeral?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** An interface representing some way to connect to other peers
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
|
|
19
|
+
* Because the network may take some time to be ready the {@link Repo} will wait
|
|
20
|
+
* until the adapter emits a `ready` event before it starts trying to use it
|
|
21
|
+
*/
|
|
22
|
+
export interface NetworkAdapterInterface extends EventEmitter<NetworkAdapterEvents> {
|
|
23
|
+
peerId?: PeerId;
|
|
24
|
+
peerMetadata?: PeerMetadata;
|
|
25
|
+
/** Called by the {@link Repo} to start the connection process
|
|
26
|
+
*
|
|
27
|
+
* @argument peerId - the peerId of this repo
|
|
28
|
+
* @argument peerMetadata - how this adapter should present itself to other peers
|
|
29
|
+
*/
|
|
30
|
+
connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
|
|
31
|
+
/** Called by the {@link Repo} to send a message to a peer
|
|
32
|
+
*
|
|
33
|
+
* @argument message - the message to send
|
|
34
|
+
*/
|
|
35
|
+
send(message: Message): void;
|
|
36
|
+
/** Called by the {@link Repo} to disconnect from the network */
|
|
37
|
+
disconnect(): void;
|
|
38
|
+
}
|
|
39
|
+
export interface NetworkAdapterEvents {
|
|
40
|
+
/** Emitted when the network is ready to be used */
|
|
41
|
+
ready: (payload: OpenPayload) => void;
|
|
42
|
+
/** Emitted when the network is closed */
|
|
43
|
+
close: () => void;
|
|
44
|
+
/** Emitted when the network adapter learns about a new peer */
|
|
45
|
+
"peer-candidate": (payload: PeerCandidatePayload) => void;
|
|
46
|
+
/** Emitted when the network adapter learns that a peer has disconnected */
|
|
47
|
+
"peer-disconnected": (payload: PeerDisconnectedPayload) => void;
|
|
48
|
+
/** Emitted when the network adapter receives a message from a peer */
|
|
49
|
+
message: (payload: Message) => void;
|
|
50
|
+
}
|
|
51
|
+
export interface OpenPayload {
|
|
52
|
+
network: NetworkAdapterInterface;
|
|
53
|
+
}
|
|
54
|
+
export interface PeerCandidatePayload {
|
|
55
|
+
peerId: PeerId;
|
|
56
|
+
peerMetadata: PeerMetadata;
|
|
57
|
+
}
|
|
58
|
+
export interface PeerDisconnectedPayload {
|
|
59
|
+
peerId: PeerId;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=NetworkAdapterInterface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetworkAdapterInterface.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapterInterface.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAwB,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,YAAY,CAAA;IAE3B;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;IAE1D;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAE5B,gEAAgE;IAChE,UAAU,IAAI,IAAI,CAAA;CACnB;AAID,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAErC,yCAAyC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAA;IAEjB,+DAA+D;IAC/D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAEzD,2EAA2E;IAC3E,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAE/D,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,uBAAuB,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { PeerId } from "../types.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { NetworkAdapterInterface, PeerDisconnectedPayload, PeerMetadata } from "./NetworkAdapterInterface.js";
|
|
4
4
|
import { MessageContents, RepoMessage } from "./messages.js";
|
|
5
5
|
export declare class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
6
6
|
#private;
|
|
7
7
|
peerId: PeerId;
|
|
8
8
|
private peerMetadata;
|
|
9
|
-
constructor(adapters:
|
|
10
|
-
addNetworkAdapter(networkAdapter:
|
|
9
|
+
constructor(adapters: NetworkAdapterInterface[], peerId: PeerId, peerMetadata: Promise<PeerMetadata>);
|
|
10
|
+
addNetworkAdapter(networkAdapter: NetworkAdapterInterface): void;
|
|
11
11
|
send(message: MessageContents): void;
|
|
12
12
|
isReady: () => boolean;
|
|
13
13
|
whenReady: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAY/D,MAAM;IACb,OAAO,CAAC,YAAY;gBAFpB,QAAQ,EAAE,uBAAuB,EAAE,EAC5B,MAAM,QAAiB,EACtB,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;IAO7C,iBAAiB,CAAC,cAAc,EAAE,uBAAuB;IA2EzD,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,sBAUR;CACF;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,WAAW,KAAK,IAAI,CAAA;IACvC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B"}
|
|
@@ -67,9 +67,11 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
|
-
this.peerMetadata
|
|
70
|
+
this.peerMetadata
|
|
71
|
+
.then(peerMetadata => {
|
|
71
72
|
networkAdapter.connect(this.peerId, peerMetadata);
|
|
72
|
-
})
|
|
73
|
+
})
|
|
74
|
+
.catch(err => {
|
|
73
75
|
this.#log("error connecting to network", err);
|
|
74
76
|
});
|
|
75
77
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { StorageAdapterInterface } from "./StorageAdapterInterface.js";
|
|
1
2
|
import { StorageKey, Chunk } from "./types.js";
|
|
2
3
|
/** A storage adapter represents some way of storing binary data for a {@link Repo}
|
|
4
|
+
* @deprecated use {@link StorageAdapterInterface}
|
|
3
5
|
*
|
|
4
6
|
* @remarks
|
|
5
7
|
* `StorageAdapter`s provide a key/value storage interface. The keys are arrays of strings
|
|
6
8
|
* ({@link StorageKey}) and the values are binary blobs.
|
|
7
9
|
*/
|
|
8
|
-
export declare abstract class StorageAdapter {
|
|
10
|
+
export declare abstract class StorageAdapter implements StorageAdapterInterface {
|
|
9
11
|
/** Load the single value corresponding to `key` */
|
|
10
12
|
abstract load(key: StorageKey): Promise<Uint8Array | undefined>;
|
|
11
13
|
/** Save the value `data` to the key `key` */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageAdapter.d.ts","sourceRoot":"","sources":["../../src/storage/StorageAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAE9C
|
|
1
|
+
{"version":3,"file":"StorageAdapter.d.ts","sourceRoot":"","sources":["../../src/storage/StorageAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;GAMG;AACH,8BAAsB,cAAe,YAAW,uBAAuB;IACrE,mDAAmD;IACnD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAE/D,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAE3D,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAC3D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { StorageKey, Chunk } from "./types.js";
|
|
2
|
+
/** A storage adapter represents some way of storing binary data for a {@link Repo}
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* `StorageAdapter`s provide a key/value storage interface. The keys are arrays of strings
|
|
6
|
+
* ({@link StorageKey}) and the values are binary blobs.
|
|
7
|
+
*/
|
|
8
|
+
export interface StorageAdapterInterface {
|
|
9
|
+
/** Load the single value corresponding to `key` */
|
|
10
|
+
load(key: StorageKey): Promise<Uint8Array | undefined>;
|
|
11
|
+
/** Save the value `data` to the key `key` */
|
|
12
|
+
save(key: StorageKey, data: Uint8Array): Promise<void>;
|
|
13
|
+
/** Remove the value corresponding to `key` */
|
|
14
|
+
remove(key: StorageKey): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Load all values with keys that start with `keyPrefix`.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* The `keyprefix` will match any key that starts with the given array. For example:
|
|
20
|
+
* - `[documentId, "incremental"]` will match all incremental saves
|
|
21
|
+
* - `[documentId]` will match all data for a given document.
|
|
22
|
+
*
|
|
23
|
+
* Be careful! `[documentId]` would also match something like `[documentId, "syncState"]`! We
|
|
24
|
+
* aren't using this yet but keep it in mind.)
|
|
25
|
+
*/
|
|
26
|
+
loadRange(keyPrefix: StorageKey): Promise<Chunk[]>;
|
|
27
|
+
/** Remove all values with keys that start with `keyPrefix` */
|
|
28
|
+
removeRange(keyPrefix: StorageKey): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=StorageAdapterInterface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageAdapterInterface.d.ts","sourceRoot":"","sources":["../../src/storage/StorageAdapterInterface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,mDAAmD;IACnD,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAA;IAEtD,6CAA6C;IAC7C,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtD,8CAA8C;IAC9C,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;IAElD,8DAA8D;IAC9D,WAAW,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge/next";
|
|
2
2
|
import { type DocumentId } from "../types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { StorageAdapterInterface } from "./StorageAdapterInterface.js";
|
|
4
4
|
import { StorageId } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* The storage subsystem is responsible for saving and loading Automerge documents to and from
|
|
@@ -8,7 +8,7 @@ import { StorageId } from "./types.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class StorageSubsystem {
|
|
10
10
|
#private;
|
|
11
|
-
constructor(storageAdapter:
|
|
11
|
+
constructor(storageAdapter: StorageAdapterInterface);
|
|
12
12
|
id(): Promise<StorageId>;
|
|
13
13
|
/** Loads a value from storage. */
|
|
14
14
|
load(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAK7D;;;GAGG;AACH,qBAAa,gBAAgB;;gBAef,cAAc,EAAE,uBAAuB;IAI7C,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAazE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAkEhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAM7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CA8CjB"}
|
|
@@ -152,7 +152,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
152
152
|
return this.#peers.includes(peerId);
|
|
153
153
|
}
|
|
154
154
|
beginSync(peerIds) {
|
|
155
|
-
const
|
|
155
|
+
const noPeersWithDocument = peerIds.every((peerId) => this.#peerDocumentStatuses[peerId] in ["unavailable", "wants"]);
|
|
156
156
|
// At this point if we don't have anything in our storage, we need to use an empty doc to sync
|
|
157
157
|
// with; but we don't want to surface that state to the front end
|
|
158
158
|
const docPromise = this.#handle
|
|
@@ -162,7 +162,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
162
162
|
this.#syncStarted = true;
|
|
163
163
|
this.#checkDocUnavailable();
|
|
164
164
|
const wasUnavailable = doc === undefined;
|
|
165
|
-
if (wasUnavailable &&
|
|
165
|
+
if (wasUnavailable && noPeersWithDocument) {
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
168
|
// If the doc is unavailable we still need a blank document to generate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A repository object to manage a collection of automerge documents",
|
|
5
5
|
"repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
|
|
6
6
|
"author": "Peter van Hardenberg <pvh@pvh.ca>",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "7e0681014b8c5f672e2abc2a653a954ccb6d7aba"
|
|
59
59
|
}
|
package/src/DocHandle.ts
CHANGED
|
@@ -39,7 +39,7 @@ export class DocHandle<T> //
|
|
|
39
39
|
#log: debug.Debugger
|
|
40
40
|
|
|
41
41
|
#machine: DocHandleXstateMachine<T>
|
|
42
|
-
#timeoutDelay
|
|
42
|
+
#timeoutDelay = 60_000
|
|
43
43
|
#remoteHeads: Record<StorageId, A.Heads> = {}
|
|
44
44
|
|
|
45
45
|
/** The URL of this document
|
|
@@ -244,13 +244,12 @@ export class DocHandle<T> //
|
|
|
244
244
|
|
|
245
245
|
/** Returns a promise that resolves when the docHandle is in one of the given states */
|
|
246
246
|
#statePromise(awaitStates: HandleState | HandleState[]) {
|
|
247
|
-
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
247
|
+
const awaitStatesArray = Array.isArray(awaitStates) ? awaitStates : [awaitStates]
|
|
248
|
+
return waitFor(
|
|
249
|
+
this.#machine,
|
|
250
|
+
s => awaitStatesArray.some((state) => s.matches(state)),
|
|
251
|
+
// use a longer delay here so as not to race with other delays
|
|
252
|
+
{timeout: this.#timeoutDelay * 2}
|
|
254
253
|
)
|
|
255
254
|
}
|
|
256
255
|
|
package/src/Repo.ts
CHANGED
|
@@ -2,18 +2,18 @@ import { next as Automerge } from "@automerge/automerge"
|
|
|
2
2
|
import debug from "debug"
|
|
3
3
|
import { EventEmitter } from "eventemitter3"
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
generateAutomergeUrl,
|
|
6
|
+
interpretAsDocumentId,
|
|
7
|
+
parseAutomergeUrl,
|
|
8
8
|
} from "./AutomergeUrl.js"
|
|
9
9
|
import { DocHandle, DocHandleEncodedChangePayload } from "./DocHandle.js"
|
|
10
10
|
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js"
|
|
11
11
|
import { headsAreSame } from "./helpers/headsAreSame.js"
|
|
12
12
|
import { throttle } from "./helpers/throttle.js"
|
|
13
|
-
import {
|
|
13
|
+
import { NetworkAdapterInterface, type PeerMetadata } from "./network/NetworkAdapterInterface.js"
|
|
14
14
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js"
|
|
15
15
|
import { RepoMessage } from "./network/messages.js"
|
|
16
|
-
import {
|
|
16
|
+
import { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js"
|
|
17
17
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js"
|
|
18
18
|
import { StorageId } from "./storage/types.js"
|
|
19
19
|
import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
|
|
@@ -155,14 +155,10 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
155
155
|
// NETWORK
|
|
156
156
|
// The network subsystem deals with sending and receiving messages to and from peers.
|
|
157
157
|
|
|
158
|
-
const myPeerMetadata: Promise<PeerMetadata> =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
storageId: await storageSubsystem?.id(),
|
|
163
|
-
isEphemeral,
|
|
164
|
-
} as PeerMetadata)
|
|
165
|
-
)
|
|
158
|
+
const myPeerMetadata: Promise<PeerMetadata> = (async () => ({
|
|
159
|
+
storageId: await storageSubsystem?.id(),
|
|
160
|
+
isEphemeral,
|
|
161
|
+
}))()
|
|
166
162
|
|
|
167
163
|
const networkSubsystem = new NetworkSubsystem(
|
|
168
164
|
network,
|
|
@@ -517,10 +513,10 @@ export interface RepoConfig {
|
|
|
517
513
|
isEphemeral?: boolean
|
|
518
514
|
|
|
519
515
|
/** A storage adapter can be provided, or not */
|
|
520
|
-
storage?:
|
|
516
|
+
storage?: StorageAdapterInterface
|
|
521
517
|
|
|
522
518
|
/** One or more network adapters must be provided */
|
|
523
|
-
network:
|
|
519
|
+
network: NetworkAdapterInterface[]
|
|
524
520
|
|
|
525
521
|
/**
|
|
526
522
|
* Normal peers typically share generously with everyone (meaning we sync all our documents with
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "assert"
|
|
2
|
-
import { describe, it } from "vitest"
|
|
3
|
-
import { PeerId, Repo,
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { PeerId, PeerMetadata, Repo, StorageId } from "../../index.js"
|
|
4
|
+
import type { NetworkAdapterInterface } from "../../network/NetworkAdapterInterface.js"
|
|
4
5
|
import { eventPromise, eventPromises } from "../eventPromise.js"
|
|
5
6
|
import { pause } from "../pause.js"
|
|
6
7
|
|
|
@@ -29,7 +30,10 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
|
|
|
29
30
|
|
|
30
31
|
describe(`Adapter acceptance tests ${title ? `(${title})` : ""}`, () => {
|
|
31
32
|
it("can sync 2 repos", async () => {
|
|
32
|
-
const doTest = async (
|
|
33
|
+
const doTest = async (
|
|
34
|
+
a: NetworkAdapterInterface[],
|
|
35
|
+
b: NetworkAdapterInterface[]
|
|
36
|
+
) => {
|
|
33
37
|
const aliceRepo = new Repo({ network: a, peerId: alice })
|
|
34
38
|
const bobRepo = new Repo({ network: b, peerId: bob })
|
|
35
39
|
|
|
@@ -141,12 +145,34 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
|
|
|
141
145
|
assert.deepStrictEqual(message, alicePresenceData)
|
|
142
146
|
teardown()
|
|
143
147
|
})
|
|
148
|
+
|
|
149
|
+
it("emits a peer-candidate event with proper peer metadata when a peer connects", async () => {
|
|
150
|
+
const { adapters, teardown } = await setup()
|
|
151
|
+
const a = adapters[0][0]
|
|
152
|
+
const b = adapters[1][0]
|
|
153
|
+
|
|
154
|
+
const bPromise = eventPromise(b, "peer-candidate")
|
|
155
|
+
|
|
156
|
+
const aPeerMetadata: PeerMetadata = { storageId: "a" as StorageId }
|
|
157
|
+
|
|
158
|
+
b.connect("b" as PeerId, { storageId: "b" as StorageId })
|
|
159
|
+
a.connect("a" as PeerId, aPeerMetadata)
|
|
160
|
+
|
|
161
|
+
const peerCandidate = await bPromise
|
|
162
|
+
|
|
163
|
+
expect(peerCandidate).toMatchObject({
|
|
164
|
+
peerId: "a",
|
|
165
|
+
peerMetadata: aPeerMetadata,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
teardown()
|
|
169
|
+
})
|
|
144
170
|
})
|
|
145
171
|
}
|
|
146
172
|
|
|
147
173
|
const NO_OP = () => {}
|
|
148
174
|
|
|
149
|
-
type Network =
|
|
175
|
+
type Network = NetworkAdapterInterface | NetworkAdapterInterface[]
|
|
150
176
|
|
|
151
177
|
export type SetupFn = () => Promise<{
|
|
152
178
|
adapters: [Network, Network, Network]
|
package/src/index.ts
CHANGED
|
@@ -34,8 +34,10 @@ export {
|
|
|
34
34
|
} from "./AutomergeUrl.js"
|
|
35
35
|
export { Repo } from "./Repo.js"
|
|
36
36
|
export { NetworkAdapter } from "./network/NetworkAdapter.js"
|
|
37
|
+
export type { NetworkAdapterInterface } from "./network/NetworkAdapterInterface.js"
|
|
37
38
|
export { isRepoMessage } from "./network/messages.js"
|
|
38
39
|
export { StorageAdapter } from "./storage/StorageAdapter.js"
|
|
40
|
+
export type { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js"
|
|
39
41
|
|
|
40
42
|
/** @hidden **/
|
|
41
43
|
export * as cbor from "./helpers/cbor.js"
|
|
@@ -68,7 +70,7 @@ export type {
|
|
|
68
70
|
PeerCandidatePayload,
|
|
69
71
|
PeerDisconnectedPayload,
|
|
70
72
|
PeerMetadata,
|
|
71
|
-
} from "./network/
|
|
73
|
+
} from "./network/NetworkAdapterInterface.js"
|
|
72
74
|
|
|
73
75
|
export type {
|
|
74
76
|
DocumentUnavailableMessage,
|
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
/* c8 ignore start */
|
|
2
2
|
|
|
3
3
|
import { EventEmitter } from "eventemitter3"
|
|
4
|
+
import { NetworkAdapterEvents, PeerMetadata } from "../index.js"
|
|
4
5
|
import { PeerId } from "../types.js"
|
|
5
6
|
import { Message } from "./messages.js"
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Describes a peer intent to the system
|
|
10
|
-
* storageId: the key for syncState to decide what the other peer already has
|
|
11
|
-
* isEphemeral: to decide if we bother recording this peer's sync state
|
|
12
|
-
*
|
|
13
|
-
*/
|
|
14
|
-
export interface PeerMetadata {
|
|
15
|
-
storageId?: StorageId
|
|
16
|
-
isEphemeral?: boolean
|
|
17
|
-
}
|
|
7
|
+
import { NetworkAdapterInterface } from "./NetworkAdapterInterface.js"
|
|
18
8
|
|
|
19
9
|
/** An interface representing some way to connect to other peers
|
|
10
|
+
* @deprecated use {@link NetworkAdapterInterface}
|
|
20
11
|
*
|
|
21
12
|
* @remarks
|
|
22
13
|
* The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
|
|
23
14
|
* Because the network may take some time to be ready the {@link Repo} will wait
|
|
24
15
|
* until the adapter emits a `ready` event before it starts trying to use it
|
|
25
16
|
*/
|
|
26
|
-
export abstract class NetworkAdapter
|
|
17
|
+
export abstract class NetworkAdapter
|
|
18
|
+
extends EventEmitter<NetworkAdapterEvents>
|
|
19
|
+
implements NetworkAdapterInterface
|
|
20
|
+
{
|
|
27
21
|
peerId?: PeerId
|
|
28
22
|
peerMetadata?: PeerMetadata
|
|
29
23
|
|
|
@@ -43,35 +37,3 @@ export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents>
|
|
|
43
37
|
/** Called by the {@link Repo} to disconnect from the network */
|
|
44
38
|
abstract disconnect(): void
|
|
45
39
|
}
|
|
46
|
-
|
|
47
|
-
// events & payloads
|
|
48
|
-
|
|
49
|
-
export interface NetworkAdapterEvents {
|
|
50
|
-
/** Emitted when the network is ready to be used */
|
|
51
|
-
ready: (payload: OpenPayload) => void
|
|
52
|
-
|
|
53
|
-
/** Emitted when the network is closed */
|
|
54
|
-
close: () => void
|
|
55
|
-
|
|
56
|
-
/** Emitted when the network adapter learns about a new peer */
|
|
57
|
-
"peer-candidate": (payload: PeerCandidatePayload) => void
|
|
58
|
-
|
|
59
|
-
/** Emitted when the network adapter learns that a peer has disconnected */
|
|
60
|
-
"peer-disconnected": (payload: PeerDisconnectedPayload) => void
|
|
61
|
-
|
|
62
|
-
/** Emitted when the network adapter receives a message from a peer */
|
|
63
|
-
message: (payload: Message) => void
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface OpenPayload {
|
|
67
|
-
network: NetworkAdapter
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface PeerCandidatePayload {
|
|
71
|
-
peerId: PeerId
|
|
72
|
-
peerMetadata: PeerMetadata
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface PeerDisconnectedPayload {
|
|
76
|
-
peerId: PeerId
|
|
77
|
-
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* c8 ignore start */
|
|
2
|
+
|
|
3
|
+
import { EventEmitter } from "eventemitter3"
|
|
4
|
+
import { PeerId } from "../types.js"
|
|
5
|
+
import { Message } from "./messages.js"
|
|
6
|
+
import { StorageId } from "../storage/types.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Describes a peer intent to the system
|
|
10
|
+
* storageId: the key for syncState to decide what the other peer already has
|
|
11
|
+
* isEphemeral: to decide if we bother recording this peer's sync state
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
export interface PeerMetadata {
|
|
15
|
+
storageId?: StorageId
|
|
16
|
+
isEphemeral?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** An interface representing some way to connect to other peers
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
|
|
23
|
+
* Because the network may take some time to be ready the {@link Repo} will wait
|
|
24
|
+
* until the adapter emits a `ready` event before it starts trying to use it
|
|
25
|
+
*/
|
|
26
|
+
export interface NetworkAdapterInterface extends EventEmitter<NetworkAdapterEvents> {
|
|
27
|
+
peerId?: PeerId
|
|
28
|
+
peerMetadata?: PeerMetadata
|
|
29
|
+
|
|
30
|
+
/** Called by the {@link Repo} to start the connection process
|
|
31
|
+
*
|
|
32
|
+
* @argument peerId - the peerId of this repo
|
|
33
|
+
* @argument peerMetadata - how this adapter should present itself to other peers
|
|
34
|
+
*/
|
|
35
|
+
connect(peerId: PeerId, peerMetadata?: PeerMetadata): void
|
|
36
|
+
|
|
37
|
+
/** Called by the {@link Repo} to send a message to a peer
|
|
38
|
+
*
|
|
39
|
+
* @argument message - the message to send
|
|
40
|
+
*/
|
|
41
|
+
send(message: Message): void
|
|
42
|
+
|
|
43
|
+
/** Called by the {@link Repo} to disconnect from the network */
|
|
44
|
+
disconnect(): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// events & payloads
|
|
48
|
+
|
|
49
|
+
export interface NetworkAdapterEvents {
|
|
50
|
+
/** Emitted when the network is ready to be used */
|
|
51
|
+
ready: (payload: OpenPayload) => void
|
|
52
|
+
|
|
53
|
+
/** Emitted when the network is closed */
|
|
54
|
+
close: () => void
|
|
55
|
+
|
|
56
|
+
/** Emitted when the network adapter learns about a new peer */
|
|
57
|
+
"peer-candidate": (payload: PeerCandidatePayload) => void
|
|
58
|
+
|
|
59
|
+
/** Emitted when the network adapter learns that a peer has disconnected */
|
|
60
|
+
"peer-disconnected": (payload: PeerDisconnectedPayload) => void
|
|
61
|
+
|
|
62
|
+
/** Emitted when the network adapter receives a message from a peer */
|
|
63
|
+
message: (payload: Message) => void
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface OpenPayload {
|
|
67
|
+
network: NetworkAdapterInterface
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PeerCandidatePayload {
|
|
71
|
+
peerId: PeerId
|
|
72
|
+
peerMetadata: PeerMetadata
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PeerDisconnectedPayload {
|
|
76
|
+
peerId: PeerId
|
|
77
|
+
}
|
|
@@ -2,10 +2,10 @@ import debug from "debug"
|
|
|
2
2
|
import { EventEmitter } from "eventemitter3"
|
|
3
3
|
import { PeerId, SessionId } from "../types.js"
|
|
4
4
|
import type {
|
|
5
|
-
|
|
5
|
+
NetworkAdapterInterface,
|
|
6
6
|
PeerDisconnectedPayload,
|
|
7
7
|
PeerMetadata,
|
|
8
|
-
} from "./
|
|
8
|
+
} from "./NetworkAdapterInterface.js"
|
|
9
9
|
import {
|
|
10
10
|
EphemeralMessage,
|
|
11
11
|
MessageContents,
|
|
@@ -21,16 +21,16 @@ const getEphemeralMessageSource = (message: EphemeralMessage) =>
|
|
|
21
21
|
|
|
22
22
|
export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
23
23
|
#log: debug.Debugger
|
|
24
|
-
#adaptersByPeer: Record<PeerId,
|
|
24
|
+
#adaptersByPeer: Record<PeerId, NetworkAdapterInterface> = {}
|
|
25
25
|
|
|
26
26
|
#count = 0
|
|
27
27
|
#sessionId: SessionId = Math.random().toString(36).slice(2) as SessionId
|
|
28
28
|
#ephemeralSessionCounts: Record<EphemeralMessageSource, number> = {}
|
|
29
29
|
#readyAdapterCount = 0
|
|
30
|
-
#adapters:
|
|
30
|
+
#adapters: NetworkAdapterInterface[] = []
|
|
31
31
|
|
|
32
32
|
constructor(
|
|
33
|
-
adapters:
|
|
33
|
+
adapters: NetworkAdapterInterface[],
|
|
34
34
|
public peerId = randomPeerId(),
|
|
35
35
|
private peerMetadata: Promise<PeerMetadata>
|
|
36
36
|
) {
|
|
@@ -39,7 +39,7 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
39
39
|
adapters.forEach(a => this.addNetworkAdapter(a))
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
addNetworkAdapter(networkAdapter:
|
|
42
|
+
addNetworkAdapter(networkAdapter: NetworkAdapterInterface) {
|
|
43
43
|
this.#adapters.push(networkAdapter)
|
|
44
44
|
networkAdapter.once("ready", () => {
|
|
45
45
|
this.#readyAdapterCount++
|
|
@@ -105,11 +105,13 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
this.peerMetadata
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
this.peerMetadata
|
|
109
|
+
.then(peerMetadata => {
|
|
110
|
+
networkAdapter.connect(this.peerId, peerMetadata)
|
|
111
|
+
})
|
|
112
|
+
.catch(err => {
|
|
113
|
+
this.#log("error connecting to network", err)
|
|
114
|
+
})
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
send(message: MessageContents) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { StorageAdapterInterface } from "./StorageAdapterInterface.js"
|
|
1
2
|
import { StorageKey, Chunk } from "./types.js"
|
|
2
3
|
|
|
3
4
|
/** A storage adapter represents some way of storing binary data for a {@link Repo}
|
|
5
|
+
* @deprecated use {@link StorageAdapterInterface}
|
|
4
6
|
*
|
|
5
7
|
* @remarks
|
|
6
8
|
* `StorageAdapter`s provide a key/value storage interface. The keys are arrays of strings
|
|
7
9
|
* ({@link StorageKey}) and the values are binary blobs.
|
|
8
10
|
*/
|
|
9
|
-
export abstract class StorageAdapter {
|
|
11
|
+
export abstract class StorageAdapter implements StorageAdapterInterface {
|
|
10
12
|
/** Load the single value corresponding to `key` */
|
|
11
13
|
abstract load(key: StorageKey): Promise<Uint8Array | undefined>
|
|
12
14
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { StorageKey, Chunk } from "./types.js"
|
|
2
|
+
|
|
3
|
+
/** A storage adapter represents some way of storing binary data for a {@link Repo}
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* `StorageAdapter`s provide a key/value storage interface. The keys are arrays of strings
|
|
7
|
+
* ({@link StorageKey}) and the values are binary blobs.
|
|
8
|
+
*/
|
|
9
|
+
export interface StorageAdapterInterface {
|
|
10
|
+
/** Load the single value corresponding to `key` */
|
|
11
|
+
load(key: StorageKey): Promise<Uint8Array | undefined>
|
|
12
|
+
|
|
13
|
+
/** Save the value `data` to the key `key` */
|
|
14
|
+
save(key: StorageKey, data: Uint8Array): Promise<void>
|
|
15
|
+
|
|
16
|
+
/** Remove the value corresponding to `key` */
|
|
17
|
+
remove(key: StorageKey): Promise<void>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load all values with keys that start with `keyPrefix`.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* The `keyprefix` will match any key that starts with the given array. For example:
|
|
24
|
+
* - `[documentId, "incremental"]` will match all incremental saves
|
|
25
|
+
* - `[documentId]` will match all data for a given document.
|
|
26
|
+
*
|
|
27
|
+
* Be careful! `[documentId]` would also match something like `[documentId, "syncState"]`! We
|
|
28
|
+
* aren't using this yet but keep it in mind.)
|
|
29
|
+
*/
|
|
30
|
+
loadRange(keyPrefix: StorageKey): Promise<Chunk[]>
|
|
31
|
+
|
|
32
|
+
/** Remove all values with keys that start with `keyPrefix` */
|
|
33
|
+
removeRange(keyPrefix: StorageKey): Promise<void>
|
|
34
|
+
}
|
|
@@ -3,7 +3,7 @@ import debug from "debug"
|
|
|
3
3
|
import { headsAreSame } from "../helpers/headsAreSame.js"
|
|
4
4
|
import { mergeArrays } from "../helpers/mergeArrays.js"
|
|
5
5
|
import { type DocumentId } from "../types.js"
|
|
6
|
-
import {
|
|
6
|
+
import { StorageAdapterInterface } from "./StorageAdapterInterface.js"
|
|
7
7
|
import { ChunkInfo, StorageKey, StorageId } from "./types.js"
|
|
8
8
|
import { keyHash, headsHash } from "./keyHash.js"
|
|
9
9
|
import { chunkTypeFromKey } from "./chunkTypeFromKey.js"
|
|
@@ -15,7 +15,7 @@ import * as Uuid from "uuid"
|
|
|
15
15
|
*/
|
|
16
16
|
export class StorageSubsystem {
|
|
17
17
|
/** The storage adapter to use for saving and loading documents */
|
|
18
|
-
#storageAdapter:
|
|
18
|
+
#storageAdapter: StorageAdapterInterface
|
|
19
19
|
|
|
20
20
|
/** Record of the latest heads we've loaded or saved for each document */
|
|
21
21
|
#storedHeads: Map<DocumentId, A.Heads> = new Map()
|
|
@@ -28,7 +28,7 @@ export class StorageSubsystem {
|
|
|
28
28
|
|
|
29
29
|
#log = debug(`automerge-repo:storage-subsystem`)
|
|
30
30
|
|
|
31
|
-
constructor(storageAdapter:
|
|
31
|
+
constructor(storageAdapter: StorageAdapterInterface) {
|
|
32
32
|
this.#storageAdapter = storageAdapter
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -227,8 +227,8 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
beginSync(peerIds: PeerId[]) {
|
|
230
|
-
const
|
|
231
|
-
|
|
230
|
+
const noPeersWithDocument = peerIds.every(
|
|
231
|
+
(peerId) => this.#peerDocumentStatuses[peerId] in ["unavailable", "wants"]
|
|
232
232
|
)
|
|
233
233
|
|
|
234
234
|
// At this point if we don't have anything in our storage, we need to use an empty doc to sync
|
|
@@ -242,7 +242,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
242
242
|
this.#checkDocUnavailable()
|
|
243
243
|
|
|
244
244
|
const wasUnavailable = doc === undefined
|
|
245
|
-
if (wasUnavailable &&
|
|
245
|
+
if (wasUnavailable && noPeersWithDocument) {
|
|
246
246
|
return
|
|
247
247
|
}
|
|
248
248
|
|
package/test/DocHandle.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge/next"
|
|
2
2
|
import assert from "assert"
|
|
3
3
|
import { decode } from "cbor-x"
|
|
4
|
-
import { describe, it } from "vitest"
|
|
4
|
+
import { describe, it, vi } from "vitest"
|
|
5
5
|
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
6
6
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
7
7
|
import { pause } from "../src/helpers/pause.js"
|
|
@@ -72,6 +72,29 @@ describe("DocHandle", () => {
|
|
|
72
72
|
assert.equal(doc?.foo, "bar")
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Once there's a Repo#stop API this case should be covered in accompanying
|
|
77
|
+
* tests and the following test removed.
|
|
78
|
+
*/
|
|
79
|
+
it("no pending timers after a document is loaded", async () => {
|
|
80
|
+
vi.useFakeTimers()
|
|
81
|
+
const timerCount = vi.getTimerCount()
|
|
82
|
+
|
|
83
|
+
const handle = new DocHandle<TestDoc>(TEST_ID)
|
|
84
|
+
assert.equal(handle.isReady(), false)
|
|
85
|
+
|
|
86
|
+
handle.doc()
|
|
87
|
+
|
|
88
|
+
assert(vi.getTimerCount() > timerCount)
|
|
89
|
+
|
|
90
|
+
// simulate loading from storage
|
|
91
|
+
handle.update(doc => docFromMockStorage(doc))
|
|
92
|
+
|
|
93
|
+
assert.equal(handle.isReady(), true)
|
|
94
|
+
assert.equal(vi.getTimerCount(), timerCount)
|
|
95
|
+
vi.useRealTimers()
|
|
96
|
+
})
|
|
97
|
+
|
|
75
98
|
it("should block changes until ready()", async () => {
|
|
76
99
|
const handle = new DocHandle<TestDoc>(TEST_ID)
|
|
77
100
|
|
package/test/Repo.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messa
|
|
|
3
3
|
import assert from "assert"
|
|
4
4
|
import * as Uuid from "uuid"
|
|
5
5
|
import { describe, expect, it } from "vitest"
|
|
6
|
-
import { READY } from "../src/DocHandle.js"
|
|
6
|
+
import { HandleState, READY } from "../src/DocHandle.js"
|
|
7
7
|
import { parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
8
8
|
import {
|
|
9
9
|
generateAutomergeUrl,
|
|
@@ -1023,6 +1023,37 @@ describe("Repo", () => {
|
|
|
1023
1023
|
})
|
|
1024
1024
|
})
|
|
1025
1025
|
|
|
1026
|
+
|
|
1027
|
+
it('peer receives a document when connection is recovered', async () => {
|
|
1028
|
+
const alice = "alice" as PeerId;
|
|
1029
|
+
const bob = "bob" as PeerId;
|
|
1030
|
+
const [aliceAdapter, bobAdapter] = DummyNetworkAdapter.createConnectedPair();
|
|
1031
|
+
const aliceRepo = new Repo({
|
|
1032
|
+
network: [aliceAdapter],
|
|
1033
|
+
peerId: alice
|
|
1034
|
+
})
|
|
1035
|
+
const bobRepo = new Repo({
|
|
1036
|
+
network: [bobAdapter],
|
|
1037
|
+
peerId: bob
|
|
1038
|
+
})
|
|
1039
|
+
|
|
1040
|
+
const aliceDoc = aliceRepo.create();
|
|
1041
|
+
aliceDoc.change((doc: any) => doc.text = 'Hello world');
|
|
1042
|
+
|
|
1043
|
+
const bobDoc = bobRepo.find(aliceDoc.url);
|
|
1044
|
+
bobDoc.unavailable()
|
|
1045
|
+
await bobDoc.whenReady([HandleState.UNAVAILABLE]);
|
|
1046
|
+
|
|
1047
|
+
aliceAdapter.peerCandidate(bob);
|
|
1048
|
+
// Bob isn't yet connected to Alice and can't respond to her sync message
|
|
1049
|
+
await pause(100);
|
|
1050
|
+
bobAdapter.peerCandidate(alice);
|
|
1051
|
+
|
|
1052
|
+
await bobDoc.whenReady([HandleState.READY]);
|
|
1053
|
+
|
|
1054
|
+
assert.equal(bobDoc.isReady(), true);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1026
1057
|
describe("with peers (mesh network)", () => {
|
|
1027
1058
|
const setup = async () => {
|
|
1028
1059
|
// Set up three repos; connect Alice to Bob, Bob to Charlie, and Alice to Charlie
|
|
@@ -1,21 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pause } from "../../src/helpers/pause.js";
|
|
2
|
+
import { Message, NetworkAdapter, PeerId } from "../../src/index.js"
|
|
2
3
|
|
|
3
4
|
export class DummyNetworkAdapter extends NetworkAdapter {
|
|
4
5
|
#startReady: boolean
|
|
6
|
+
#sendMessage?: SendMessageFn;
|
|
5
7
|
|
|
6
|
-
constructor(
|
|
8
|
+
constructor(opts: Options = {startReady: true}) {
|
|
7
9
|
super()
|
|
8
|
-
this.#startReady = startReady
|
|
10
|
+
this.#startReady = opts.startReady;
|
|
11
|
+
this.#sendMessage = opts.sendMessage;
|
|
9
12
|
}
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
connect(_: string) {
|
|
12
15
|
if (this.#startReady) {
|
|
13
16
|
this.emit("ready", { network: this })
|
|
14
17
|
}
|
|
15
18
|
}
|
|
19
|
+
|
|
16
20
|
disconnect() {}
|
|
21
|
+
|
|
22
|
+
peerCandidate(peerId: PeerId) {
|
|
23
|
+
this.emit('peer-candidate', { peerId, peerMetadata: {} });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override send(message: Message) {
|
|
27
|
+
this.#sendMessage?.(message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
receive(message: Message) {
|
|
31
|
+
this.emit('message', message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static createConnectedPair() {
|
|
35
|
+
const adapter1: DummyNetworkAdapter = new DummyNetworkAdapter({
|
|
36
|
+
startReady: true,
|
|
37
|
+
sendMessage: (message: Message) => pause(10).then(() => adapter2.receive(message)),
|
|
38
|
+
});
|
|
39
|
+
const adapter2: DummyNetworkAdapter = new DummyNetworkAdapter({
|
|
40
|
+
startReady: true,
|
|
41
|
+
sendMessage: (message: Message) => pause(10).then(() => adapter1.receive(message)),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return [adapter1, adapter2];
|
|
45
|
+
}
|
|
17
46
|
}
|
|
18
47
|
|
|
48
|
+
type SendMessageFn = (message: Message) => void;
|
|
49
|
+
|
|
19
50
|
type Options = {
|
|
20
|
-
startReady?: boolean
|
|
51
|
+
startReady?: boolean;
|
|
52
|
+
sendMessage?: SendMessageFn;
|
|
21
53
|
}
|