@automerge/automerge-repo 1.2.1 → 2.0.0-alpha.0
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/AutomergeUrl.d.ts +3 -3
- package/dist/AutomergeUrl.d.ts.map +1 -1
- package/dist/AutomergeUrl.js +5 -1
- package/dist/DocHandle.d.ts +11 -10
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +23 -43
- package/dist/Repo.d.ts +1 -1
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +53 -36
- package/dist/helpers/DummyNetworkAdapter.d.ts +3 -0
- package/dist/helpers/DummyNetworkAdapter.d.ts.map +1 -1
- package/dist/helpers/DummyNetworkAdapter.js +24 -5
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +88 -1
- package/dist/helpers/throttle.d.ts +1 -1
- package/dist/helpers/throttle.js +1 -1
- package/dist/network/NetworkAdapter.d.ts +2 -0
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkAdapterInterface.d.ts +2 -2
- package/dist/network/NetworkAdapterInterface.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.d.ts +5 -2
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +21 -25
- package/package.json +3 -3
- package/src/AutomergeUrl.ts +6 -6
- package/src/DocHandle.ts +27 -57
- package/src/Repo.ts +55 -40
- package/src/helpers/DummyNetworkAdapter.ts +29 -5
- package/src/helpers/tests/network-adapter-tests.ts +121 -1
- package/src/helpers/throttle.ts +1 -1
- package/src/network/NetworkAdapter.ts +3 -0
- package/src/network/NetworkAdapterInterface.ts +4 -3
- package/src/network/NetworkSubsystem.ts +24 -31
- package/test/AutomergeUrl.test.ts +4 -0
- package/test/DocHandle.test.ts +20 -24
- package/test/DocSynchronizer.test.ts +5 -1
- package/test/NetworkSubsystem.test.ts +107 -0
- package/test/Repo.test.ts +37 -15
- package/test/remoteHeads.test.ts +3 -3
- package/test/Network.test.ts +0 -14
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import assert from "assert"
|
|
2
2
|
import { describe, expect, it } from "vitest"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
generateAutomergeUrl,
|
|
5
|
+
parseAutomergeUrl,
|
|
6
|
+
PeerId,
|
|
7
|
+
PeerMetadata,
|
|
8
|
+
Repo,
|
|
9
|
+
StorageId,
|
|
10
|
+
} from "../../index.js"
|
|
4
11
|
import type { NetworkAdapterInterface } from "../../network/NetworkAdapterInterface.js"
|
|
5
12
|
import { eventPromise, eventPromises } from "../eventPromise.js"
|
|
6
13
|
import { pause } from "../pause.js"
|
|
@@ -169,6 +176,119 @@ export function runNetworkAdapterTests(_setup: SetupFn, title?: string): void {
|
|
|
169
176
|
|
|
170
177
|
teardown()
|
|
171
178
|
})
|
|
179
|
+
|
|
180
|
+
it("should emit disconnect events on disconnect", async () => {
|
|
181
|
+
const { adapters, teardown } = await setup()
|
|
182
|
+
const left = adapters[0][0]
|
|
183
|
+
const right = adapters[1][0]
|
|
184
|
+
|
|
185
|
+
const leftPeerId = "left" as PeerId
|
|
186
|
+
const rightPeerId = "right" as PeerId
|
|
187
|
+
|
|
188
|
+
const leftRepo = new Repo({
|
|
189
|
+
network: [left],
|
|
190
|
+
peerId: leftPeerId,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const rightRepo = new Repo({
|
|
194
|
+
network: [right],
|
|
195
|
+
peerId: rightPeerId,
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await Promise.all([
|
|
199
|
+
eventPromise(leftRepo.networkSubsystem, "peer"),
|
|
200
|
+
eventPromise(rightRepo.networkSubsystem, "peer"),
|
|
201
|
+
])
|
|
202
|
+
|
|
203
|
+
const disconnectionPromises = Promise.all([
|
|
204
|
+
eventPromise(leftRepo.networkSubsystem, "peer-disconnected"),
|
|
205
|
+
eventPromise(rightRepo.networkSubsystem, "peer-disconnected"),
|
|
206
|
+
])
|
|
207
|
+
left.disconnect()
|
|
208
|
+
|
|
209
|
+
await disconnectionPromises
|
|
210
|
+
teardown()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("should not send messages after disconnect", async () => {
|
|
214
|
+
const { adapters, teardown } = await setup()
|
|
215
|
+
const left = adapters[0][0]
|
|
216
|
+
const right = adapters[1][0]
|
|
217
|
+
|
|
218
|
+
const leftPeerId = "left" as PeerId
|
|
219
|
+
const rightPeerId = "right" as PeerId
|
|
220
|
+
|
|
221
|
+
const leftRepo = new Repo({
|
|
222
|
+
network: [left],
|
|
223
|
+
peerId: leftPeerId,
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const rightRepo = new Repo({
|
|
227
|
+
network: [right],
|
|
228
|
+
peerId: rightPeerId,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await Promise.all([
|
|
232
|
+
eventPromise(rightRepo.networkSubsystem, "peer"),
|
|
233
|
+
eventPromise(leftRepo.networkSubsystem, "peer"),
|
|
234
|
+
])
|
|
235
|
+
|
|
236
|
+
const disconnected = eventPromise(right, "peer-disconnected")
|
|
237
|
+
|
|
238
|
+
left.disconnect()
|
|
239
|
+
await disconnected
|
|
240
|
+
|
|
241
|
+
const rightReceivedFromLeft = new Promise(resolve => {
|
|
242
|
+
right.on("message", msg => {
|
|
243
|
+
if (msg.senderId === leftPeerId) {
|
|
244
|
+
resolve(null)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
const rightReceived = Promise.race([rightReceivedFromLeft, pause(10)])
|
|
250
|
+
|
|
251
|
+
const documentId = parseAutomergeUrl(generateAutomergeUrl()).documentId
|
|
252
|
+
left.send({
|
|
253
|
+
type: "foo",
|
|
254
|
+
data: new Uint8Array([1, 2, 3]),
|
|
255
|
+
documentId,
|
|
256
|
+
senderId: leftPeerId,
|
|
257
|
+
targetId: rightPeerId,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
assert.equal(await rightReceived, null)
|
|
261
|
+
teardown()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("should support reconnecting after disconnect", async () => {
|
|
265
|
+
const { adapters, teardown } = await setup()
|
|
266
|
+
const left = adapters[0][0]
|
|
267
|
+
const right = adapters[1][0]
|
|
268
|
+
|
|
269
|
+
const leftPeerId = "left" as PeerId
|
|
270
|
+
const rightPeerId = "right" as PeerId
|
|
271
|
+
|
|
272
|
+
const _leftRepo = new Repo({
|
|
273
|
+
network: [left],
|
|
274
|
+
peerId: leftPeerId,
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const rightRepo = new Repo({
|
|
278
|
+
network: [right],
|
|
279
|
+
peerId: rightPeerId,
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
await eventPromise(rightRepo.networkSubsystem, "peer")
|
|
283
|
+
|
|
284
|
+
left.disconnect()
|
|
285
|
+
|
|
286
|
+
await pause(10)
|
|
287
|
+
|
|
288
|
+
left.connect(leftPeerId)
|
|
289
|
+
await eventPromise(left, "peer-candidate")
|
|
290
|
+
teardown()
|
|
291
|
+
})
|
|
172
292
|
})
|
|
173
293
|
}
|
|
174
294
|
|
package/src/helpers/throttle.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*
|
|
22
22
|
* Example usage:
|
|
23
|
-
* const callback =
|
|
23
|
+
* const callback = throttle((ev) => { doSomethingExpensiveOrOccasional() }, 100)
|
|
24
24
|
* target.addEventListener('frequent-event', callback);
|
|
25
25
|
*
|
|
26
26
|
*/
|
|
@@ -23,6 +23,9 @@ export abstract class NetworkAdapter
|
|
|
23
23
|
peerId?: PeerId
|
|
24
24
|
peerMetadata?: PeerMetadata
|
|
25
25
|
|
|
26
|
+
abstract isReady(): boolean
|
|
27
|
+
abstract whenReady(): Promise<void>
|
|
28
|
+
|
|
26
29
|
/** Called by the {@link Repo} to start the connection process
|
|
27
30
|
*
|
|
28
31
|
* @argument peerId - the peerId of this repo
|
|
@@ -32,12 +32,16 @@ export interface NetworkAdapterInterface
|
|
|
32
32
|
peerId?: PeerId
|
|
33
33
|
peerMetadata?: PeerMetadata
|
|
34
34
|
|
|
35
|
+
isReady(): boolean
|
|
36
|
+
whenReady(): Promise<void>
|
|
37
|
+
|
|
35
38
|
/** Called by the {@link Repo} to start the connection process
|
|
36
39
|
*
|
|
37
40
|
* @argument peerId - the peerId of this repo
|
|
38
41
|
* @argument peerMetadata - how this adapter should present itself to other peers
|
|
39
42
|
*/
|
|
40
43
|
connect(peerId: PeerId, peerMetadata?: PeerMetadata): void
|
|
44
|
+
// TODO: should this just return a ready promise?
|
|
41
45
|
|
|
42
46
|
/** Called by the {@link Repo} to send a message to a peer
|
|
43
47
|
*
|
|
@@ -52,9 +56,6 @@ export interface NetworkAdapterInterface
|
|
|
52
56
|
// events & payloads
|
|
53
57
|
|
|
54
58
|
export interface NetworkAdapterEvents {
|
|
55
|
-
/** Emitted when the network is ready to be used */
|
|
56
|
-
ready: (payload: OpenPayload) => void
|
|
57
|
-
|
|
58
59
|
/** Emitted when the network is closed */
|
|
59
60
|
close: () => void
|
|
60
61
|
|
|
@@ -26,12 +26,11 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
26
26
|
#count = 0
|
|
27
27
|
#sessionId: SessionId = Math.random().toString(36).slice(2) as SessionId
|
|
28
28
|
#ephemeralSessionCounts: Record<EphemeralMessageSource, number> = {}
|
|
29
|
-
|
|
30
|
-
#adapters: NetworkAdapterInterface[] = []
|
|
29
|
+
adapters: NetworkAdapterInterface[] = []
|
|
31
30
|
|
|
32
31
|
constructor(
|
|
33
32
|
adapters: NetworkAdapterInterface[],
|
|
34
|
-
public peerId
|
|
33
|
+
public peerId: PeerId,
|
|
35
34
|
private peerMetadata: Promise<PeerMetadata>
|
|
36
35
|
) {
|
|
37
36
|
super()
|
|
@@ -39,25 +38,25 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
39
38
|
adapters.forEach(a => this.addNetworkAdapter(a))
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
disconnect() {
|
|
42
|
+
this.adapters.forEach(a => a.disconnect())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
reconnect() {
|
|
46
|
+
this.adapters.forEach(a => a.connect(this.peerId))
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
addNetworkAdapter(networkAdapter: NetworkAdapterInterface) {
|
|
43
|
-
this
|
|
44
|
-
networkAdapter.once("ready", () => {
|
|
45
|
-
this.#readyAdapterCount++
|
|
46
|
-
this.#log(
|
|
47
|
-
"Adapters ready: ",
|
|
48
|
-
this.#readyAdapterCount,
|
|
49
|
-
"/",
|
|
50
|
-
this.#adapters.length
|
|
51
|
-
)
|
|
52
|
-
if (this.#readyAdapterCount === this.#adapters.length) {
|
|
53
|
-
this.emit("ready")
|
|
54
|
-
}
|
|
55
|
-
})
|
|
50
|
+
this.adapters.push(networkAdapter)
|
|
56
51
|
|
|
57
52
|
networkAdapter.on("peer-candidate", ({ peerId, peerMetadata }) => {
|
|
58
53
|
this.#log(`peer candidate: ${peerId} `)
|
|
59
54
|
// TODO: This is where authentication would happen
|
|
60
55
|
|
|
56
|
+
// TODO: on reconnection, this would create problems!
|
|
57
|
+
// the server would see a reconnection as a late-arriving channel
|
|
58
|
+
// for an existing peer and decide to ignore it until the connection
|
|
59
|
+
// times out: turns out my ICE/SIP emulation laziness did not pay off here
|
|
61
60
|
if (!this.#adaptersByPeer[peerId]) {
|
|
62
61
|
// TODO: handle losing a server here
|
|
63
62
|
this.#adaptersByPeer[peerId] = networkAdapter
|
|
@@ -114,6 +113,13 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
114
113
|
})
|
|
115
114
|
}
|
|
116
115
|
|
|
116
|
+
// TODO: this probably introduces a race condition for the ready event
|
|
117
|
+
// but I plan to refactor that as part of this branch in another patch
|
|
118
|
+
removeNetworkAdapter(networkAdapter: NetworkAdapterInterface) {
|
|
119
|
+
this.adapters = this.adapters.filter(a => a !== networkAdapter)
|
|
120
|
+
networkAdapter.disconnect()
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
send(message: MessageContents) {
|
|
118
124
|
const peer = this.#adaptersByPeer[message.targetId]
|
|
119
125
|
if (!peer) {
|
|
@@ -153,33 +159,20 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
isReady = () => {
|
|
156
|
-
return this
|
|
162
|
+
return this.adapters.every(a => a.isReady())
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
whenReady = async () => {
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
} else {
|
|
163
|
-
return new Promise<void>(resolve => {
|
|
164
|
-
this.once("ready", () => {
|
|
165
|
-
resolve()
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
}
|
|
166
|
+
return Promise.all(this.adapters.map(a => a.whenReady()))
|
|
169
167
|
}
|
|
170
168
|
}
|
|
171
169
|
|
|
172
|
-
function randomPeerId() {
|
|
173
|
-
return `user-${Math.round(Math.random() * 100000)}` as PeerId
|
|
174
|
-
}
|
|
175
|
-
|
|
176
170
|
// events & payloads
|
|
177
171
|
|
|
178
172
|
export interface NetworkSubsystemEvents {
|
|
179
173
|
peer: (payload: PeerPayload) => void
|
|
180
174
|
"peer-disconnected": (payload: PeerDisconnectedPayload) => void
|
|
181
175
|
message: (payload: RepoMessage) => void
|
|
182
|
-
ready: () => void
|
|
183
176
|
}
|
|
184
177
|
|
|
185
178
|
export interface PeerPayload {
|
|
@@ -96,5 +96,9 @@ describe("AutomergeUrl", () => {
|
|
|
96
96
|
const url = stringifyAutomergeUrl({ documentId: badUuidDocumentId })
|
|
97
97
|
assert(isValidAutomergeUrl(url) === false)
|
|
98
98
|
})
|
|
99
|
+
|
|
100
|
+
it("should return false for a documentId that is just some random type", () => {
|
|
101
|
+
assert(isValidAutomergeUrl({ foo: "bar" } as unknown) === false)
|
|
102
|
+
})
|
|
99
103
|
})
|
|
100
104
|
})
|
package/test/DocHandle.test.ts
CHANGED
|
@@ -10,6 +10,12 @@ import { TestDoc } from "./types.js"
|
|
|
10
10
|
|
|
11
11
|
describe("DocHandle", () => {
|
|
12
12
|
const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId
|
|
13
|
+
const setup = (options?) => {
|
|
14
|
+
const handle = new DocHandle<TestDoc>(TEST_ID, options)
|
|
15
|
+
handle.update(() => A.init())
|
|
16
|
+
handle.doneLoading()
|
|
17
|
+
return handle
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
const docFromMockStorage = (doc: A.Doc<{ foo: string }>) => {
|
|
15
21
|
return A.change<{ foo: string }>(doc, d => (d.foo = "bar"))
|
|
@@ -20,15 +26,6 @@ describe("DocHandle", () => {
|
|
|
20
26
|
assert.equal(handle.documentId, TEST_ID)
|
|
21
27
|
})
|
|
22
28
|
|
|
23
|
-
it("should take an initial value", async () => {
|
|
24
|
-
const handle = new DocHandle(TEST_ID, {
|
|
25
|
-
isNew: true,
|
|
26
|
-
initialValue: { foo: "bar" },
|
|
27
|
-
})
|
|
28
|
-
const doc = await handle.doc()
|
|
29
|
-
assert.equal(doc.foo, "bar")
|
|
30
|
-
})
|
|
31
|
-
|
|
32
29
|
it("should become ready when a document is loaded", async () => {
|
|
33
30
|
const handle = new DocHandle<TestDoc>(TEST_ID)
|
|
34
31
|
assert.equal(handle.isReady(), false)
|
|
@@ -55,7 +52,6 @@ describe("DocHandle", () => {
|
|
|
55
52
|
|
|
56
53
|
it("should return undefined if we access the doc before ready", async () => {
|
|
57
54
|
const handle = new DocHandle<TestDoc>(TEST_ID)
|
|
58
|
-
|
|
59
55
|
assert.equal(handle.docSync(), undefined)
|
|
60
56
|
})
|
|
61
57
|
|
|
@@ -73,10 +69,8 @@ describe("DocHandle", () => {
|
|
|
73
69
|
})
|
|
74
70
|
|
|
75
71
|
it("should return the heads when requested", async () => {
|
|
76
|
-
const handle =
|
|
77
|
-
|
|
78
|
-
initialValue: { foo: "bar" },
|
|
79
|
-
})
|
|
72
|
+
const handle = setup()
|
|
73
|
+
handle.change(d => (d.foo = "bar"))
|
|
80
74
|
assert.equal(handle.isReady(), true)
|
|
81
75
|
|
|
82
76
|
const heads = A.getHeads(handle.docSync())
|
|
@@ -94,6 +88,7 @@ describe("DocHandle", () => {
|
|
|
94
88
|
* Once there's a Repo#stop API this case should be covered in accompanying
|
|
95
89
|
* tests and the following test removed.
|
|
96
90
|
*/
|
|
91
|
+
// TODO as part of future cleanup: move this to Repo
|
|
97
92
|
it("no pending timers after a document is loaded", async () => {
|
|
98
93
|
vi.useFakeTimers()
|
|
99
94
|
const timerCount = vi.getTimerCount()
|
|
@@ -159,7 +154,7 @@ describe("DocHandle", () => {
|
|
|
159
154
|
})
|
|
160
155
|
|
|
161
156
|
it("should emit a change message when changes happen", async () => {
|
|
162
|
-
const handle =
|
|
157
|
+
const handle = setup()
|
|
163
158
|
|
|
164
159
|
const p = new Promise<DocHandleChangePayload<TestDoc>>(resolve =>
|
|
165
160
|
handle.once("change", d => resolve(d))
|
|
@@ -179,7 +174,7 @@ describe("DocHandle", () => {
|
|
|
179
174
|
|
|
180
175
|
it("should not emit a change message if no change happens via update", () =>
|
|
181
176
|
new Promise<void>((done, reject) => {
|
|
182
|
-
const handle =
|
|
177
|
+
const handle = setup()
|
|
183
178
|
handle.once("change", () => {
|
|
184
179
|
reject(new Error("shouldn't have changed"))
|
|
185
180
|
})
|
|
@@ -190,7 +185,7 @@ describe("DocHandle", () => {
|
|
|
190
185
|
}))
|
|
191
186
|
|
|
192
187
|
it("should update the internal doc prior to emitting the change message", async () => {
|
|
193
|
-
const handle =
|
|
188
|
+
const handle = setup()
|
|
194
189
|
|
|
195
190
|
const p = new Promise<void>(resolve =>
|
|
196
191
|
handle.once("change", ({ handle, doc }) => {
|
|
@@ -208,7 +203,7 @@ describe("DocHandle", () => {
|
|
|
208
203
|
})
|
|
209
204
|
|
|
210
205
|
it("should emit distinct change messages when consecutive changes happen", async () => {
|
|
211
|
-
const handle =
|
|
206
|
+
const handle = setup()
|
|
212
207
|
|
|
213
208
|
let calls = 0
|
|
214
209
|
const p = new Promise(resolve =>
|
|
@@ -238,7 +233,7 @@ describe("DocHandle", () => {
|
|
|
238
233
|
})
|
|
239
234
|
|
|
240
235
|
it("should emit a change message when changes happen", async () => {
|
|
241
|
-
const handle =
|
|
236
|
+
const handle = setup()
|
|
242
237
|
const p = new Promise(resolve => handle.once("change", d => resolve(d)))
|
|
243
238
|
|
|
244
239
|
handle.change(doc => {
|
|
@@ -252,7 +247,7 @@ describe("DocHandle", () => {
|
|
|
252
247
|
|
|
253
248
|
it("should not emit a patch message if no change happens", () =>
|
|
254
249
|
new Promise<void>((done, reject) => {
|
|
255
|
-
const handle =
|
|
250
|
+
const handle = setup()
|
|
256
251
|
handle.on("change", () => {
|
|
257
252
|
reject(new Error("shouldn't have changed"))
|
|
258
253
|
})
|
|
@@ -301,7 +296,7 @@ describe("DocHandle", () => {
|
|
|
301
296
|
|
|
302
297
|
it("should not time out if the document is updated in time", async () => {
|
|
303
298
|
// set docHandle time out after 5 ms
|
|
304
|
-
const handle =
|
|
299
|
+
const handle = setup({ timeoutDelay: 1 })
|
|
305
300
|
|
|
306
301
|
// simulate requesting from the network
|
|
307
302
|
handle.request()
|
|
@@ -319,7 +314,7 @@ describe("DocHandle", () => {
|
|
|
319
314
|
})
|
|
320
315
|
|
|
321
316
|
it("should emit a delete event when deleted", async () => {
|
|
322
|
-
const handle =
|
|
317
|
+
const handle = setup()
|
|
323
318
|
|
|
324
319
|
const p = new Promise<void>(resolve =>
|
|
325
320
|
handle.once("delete", () => resolve())
|
|
@@ -331,7 +326,7 @@ describe("DocHandle", () => {
|
|
|
331
326
|
})
|
|
332
327
|
|
|
333
328
|
it("should allow changing at old heads", async () => {
|
|
334
|
-
const handle =
|
|
329
|
+
const handle = setup()
|
|
335
330
|
|
|
336
331
|
handle.change(doc => {
|
|
337
332
|
doc.foo = "bar"
|
|
@@ -355,7 +350,8 @@ describe("DocHandle", () => {
|
|
|
355
350
|
|
|
356
351
|
describe("ephemeral messaging", () => {
|
|
357
352
|
it("can broadcast a message for the network to send out", async () => {
|
|
358
|
-
const handle =
|
|
353
|
+
const handle = setup()
|
|
354
|
+
|
|
359
355
|
const message = { foo: "bar" }
|
|
360
356
|
|
|
361
357
|
const promise = eventPromise(handle, "ephemeral-message-outbound")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "assert"
|
|
2
2
|
import { describe, it } from "vitest"
|
|
3
|
+
import { next as Automerge } from "@automerge/automerge"
|
|
3
4
|
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
4
5
|
import { DocHandle } from "../src/DocHandle.js"
|
|
5
6
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
@@ -21,10 +22,13 @@ describe("DocSynchronizer", () => {
|
|
|
21
22
|
|
|
22
23
|
const setup = () => {
|
|
23
24
|
const docId = parseAutomergeUrl(generateAutomergeUrl()).documentId
|
|
24
|
-
handle = new DocHandle<TestDoc>(docId
|
|
25
|
+
handle = new DocHandle<TestDoc>(docId)
|
|
26
|
+
handle.doneLoading()
|
|
27
|
+
|
|
25
28
|
docSynchronizer = new DocSynchronizer({
|
|
26
29
|
handle: handle as DocHandle<unknown>,
|
|
27
30
|
})
|
|
31
|
+
|
|
28
32
|
return { handle, docSynchronizer }
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from "assert"
|
|
2
|
+
import { describe, it } from "vitest"
|
|
3
|
+
import { NetworkSubsystem } from "../src/network/NetworkSubsystem.js"
|
|
4
|
+
import { DummyNetworkAdapter } from "../src/helpers/DummyNetworkAdapter.js"
|
|
5
|
+
import { PeerId, PeerMetadata, StorageId } from "../src/index.js"
|
|
6
|
+
|
|
7
|
+
// Note: The sync tests in `Repo.test.ts` exercise the network subsystem, and the suite in
|
|
8
|
+
// `automerge-repo` provides test utilities that individual adapters can
|
|
9
|
+
// use to ensure that they work correctly.
|
|
10
|
+
|
|
11
|
+
describe("Network subsystem", () => {
|
|
12
|
+
const setup = ({ startReady = true } = {}) => {
|
|
13
|
+
const networkAdapter = new DummyNetworkAdapter({ startReady })
|
|
14
|
+
const peerId = "peerId" as PeerId
|
|
15
|
+
const peerMetadata: Promise<PeerMetadata> = Promise.resolve({
|
|
16
|
+
storageId: "no-such-id" as StorageId,
|
|
17
|
+
isEphemeral: true,
|
|
18
|
+
})
|
|
19
|
+
return { networkAdapter, peerId, peerMetadata }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
it("Can be instantiated with no network adapters", () => {
|
|
23
|
+
const { peerId, peerMetadata } = setup()
|
|
24
|
+
const network = new NetworkSubsystem([], peerId, peerMetadata)
|
|
25
|
+
assert(network !== null)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("can be instantiated with a network adapter", async () => {
|
|
29
|
+
const { networkAdapter, peerId, peerMetadata } = setup()
|
|
30
|
+
const network = new NetworkSubsystem([networkAdapter], peerId, peerMetadata)
|
|
31
|
+
assert(
|
|
32
|
+
network instanceof NetworkSubsystem,
|
|
33
|
+
"Network should be an instance of NetworkSubsystem"
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("should become ready with a ready adapter", async () => {
|
|
38
|
+
const { networkAdapter, peerId, peerMetadata } = setup()
|
|
39
|
+
const network = new NetworkSubsystem([networkAdapter], peerId, peerMetadata)
|
|
40
|
+
await network.whenReady()
|
|
41
|
+
assert.strictEqual(
|
|
42
|
+
network.isReady(),
|
|
43
|
+
true,
|
|
44
|
+
"Network should be ready with a ready adapter"
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("allows adding a network adapter after creation", async () => {
|
|
49
|
+
const { networkAdapter, peerId, peerMetadata } = setup({
|
|
50
|
+
startReady: false,
|
|
51
|
+
})
|
|
52
|
+
const network = new NetworkSubsystem([], peerId, peerMetadata)
|
|
53
|
+
network.addNetworkAdapter(networkAdapter)
|
|
54
|
+
assert(
|
|
55
|
+
network instanceof NetworkSubsystem,
|
|
56
|
+
"Network should be an instance of NetworkSubsystem"
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("does not become ready prematurely when adapter is not ready", async () => {
|
|
61
|
+
const { networkAdapter, peerId, peerMetadata } = setup({
|
|
62
|
+
startReady: false,
|
|
63
|
+
})
|
|
64
|
+
const network = new NetworkSubsystem([], peerId, peerMetadata)
|
|
65
|
+
network.addNetworkAdapter(networkAdapter)
|
|
66
|
+
assert.strictEqual(
|
|
67
|
+
network.isReady(),
|
|
68
|
+
false,
|
|
69
|
+
"Network should not be ready immediately when adapter is not ready"
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("allows removing a network adapter after creation", async () => {
|
|
74
|
+
const { networkAdapter, peerId, peerMetadata } = setup()
|
|
75
|
+
const network = new NetworkSubsystem([networkAdapter], peerId, peerMetadata)
|
|
76
|
+
network.removeNetworkAdapter(networkAdapter)
|
|
77
|
+
assert(network !== null)
|
|
78
|
+
assert(network.adapters.length == 0)
|
|
79
|
+
assert.strictEqual(
|
|
80
|
+
network.isReady(),
|
|
81
|
+
true,
|
|
82
|
+
"An empty network subsystem is ready after removing the adapter"
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("handles ready behaviour for multiple network adapters correctly", async () => {
|
|
87
|
+
const { networkAdapter, peerId, peerMetadata } = setup()
|
|
88
|
+
const network = new NetworkSubsystem([networkAdapter], peerId, peerMetadata)
|
|
89
|
+
|
|
90
|
+
const anotherAdapter = new DummyNetworkAdapter({ startReady: true })
|
|
91
|
+
network.addNetworkAdapter(anotherAdapter)
|
|
92
|
+
|
|
93
|
+
await network.whenReady()
|
|
94
|
+
assert.strictEqual(
|
|
95
|
+
network.isReady(),
|
|
96
|
+
true,
|
|
97
|
+
"Network should be ready when all adapters are ready"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
network.removeNetworkAdapter(networkAdapter)
|
|
101
|
+
assert.strictEqual(
|
|
102
|
+
network.isReady(),
|
|
103
|
+
true,
|
|
104
|
+
"Network should remain ready when at least one adapter is still ready"
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
})
|