@automerge/automerge-repo 1.1.0-alpha.6 → 1.1.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/README.md +12 -7
- package/dist/AutomergeUrl.js +2 -2
- package/dist/DocHandle.d.ts +10 -4
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +17 -8
- package/dist/RemoteHeadsSubscriptions.js +3 -3
- package/dist/Repo.d.ts +23 -6
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +104 -71
- package/dist/helpers/debounce.js +1 -1
- package/dist/helpers/pause.d.ts +0 -1
- package/dist/helpers/pause.d.ts.map +1 -1
- package/dist/helpers/pause.js +2 -8
- package/dist/helpers/throttle.js +1 -1
- package/dist/helpers/withTimeout.d.ts.map +1 -1
- package/dist/helpers/withTimeout.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.js +2 -1
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +5 -3
- package/dist/network/messages.d.ts +43 -38
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/network/messages.js +7 -9
- package/dist/storage/StorageSubsystem.js +1 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +1 -0
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +13 -5
- package/dist/synchronizer/Synchronizer.d.ts +11 -3
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/AutomergeUrl.ts +2 -2
- package/src/DocHandle.ts +34 -12
- package/src/RemoteHeadsSubscriptions.ts +3 -3
- package/src/Repo.ts +130 -81
- package/src/helpers/debounce.ts +1 -1
- package/src/helpers/pause.ts +3 -11
- package/src/helpers/throttle.ts +1 -1
- package/src/helpers/withTimeout.ts +2 -0
- package/src/index.ts +1 -1
- package/src/network/NetworkAdapter.ts +5 -3
- package/src/network/NetworkSubsystem.ts +5 -4
- package/src/network/messages.ts +60 -63
- package/src/storage/StorageSubsystem.ts +1 -1
- package/src/synchronizer/CollectionSynchronizer.ts +2 -1
- package/src/synchronizer/DocSynchronizer.ts +19 -11
- package/src/synchronizer/Synchronizer.ts +11 -3
- package/test/CollectionSynchronizer.test.ts +7 -5
- package/test/DocHandle.test.ts +11 -2
- package/test/RemoteHeadsSubscriptions.test.ts +53 -50
- package/test/Repo.test.ts +64 -2
- package/test/StorageSubsystem.test.ts +1 -1
- package/test/helpers/collectMessages.ts +19 -0
- package/test/remoteHeads.test.ts +141 -120
- package/.eslintrc +0 -28
- package/test/helpers/waitForMessages.ts +0 -22
package/test/Repo.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { next as A } from "@automerge/automerge"
|
|
2
|
-
import { MessageChannelNetworkAdapter } from "
|
|
2
|
+
import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/src/index.js"
|
|
3
3
|
import assert from "assert"
|
|
4
4
|
import * as Uuid from "uuid"
|
|
5
|
-
import { describe, it } from "vitest"
|
|
5
|
+
import { describe, expect, it } from "vitest"
|
|
6
6
|
import { READY } from "../src/DocHandle.js"
|
|
7
7
|
import { parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
8
8
|
import {
|
|
@@ -13,6 +13,7 @@ import { Repo } from "../src/Repo.js"
|
|
|
13
13
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
14
14
|
import { pause } from "../src/helpers/pause.js"
|
|
15
15
|
import {
|
|
16
|
+
AnyDocumentId,
|
|
16
17
|
AutomergeUrl,
|
|
17
18
|
DocHandle,
|
|
18
19
|
DocumentId,
|
|
@@ -31,6 +32,15 @@ import { TestDoc } from "./types.js"
|
|
|
31
32
|
import { StorageId } from "../src/storage/types.js"
|
|
32
33
|
|
|
33
34
|
describe("Repo", () => {
|
|
35
|
+
describe("constructor", () => {
|
|
36
|
+
it("can be instantiated without network adapters", () => {
|
|
37
|
+
const repo = new Repo({
|
|
38
|
+
network: [],
|
|
39
|
+
})
|
|
40
|
+
expect(repo).toBeInstanceOf(Repo)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
34
44
|
describe("local only", () => {
|
|
35
45
|
const setup = ({ startReady = true } = {}) => {
|
|
36
46
|
const storageAdapter = new DummyStorageAdapter()
|
|
@@ -57,6 +67,13 @@ describe("Repo", () => {
|
|
|
57
67
|
assert.equal(handle.isReady(), true)
|
|
58
68
|
})
|
|
59
69
|
|
|
70
|
+
it("can create a document with an initial value", async () => {
|
|
71
|
+
const { repo } = setup()
|
|
72
|
+
const handle = repo.create({ foo: "bar" })
|
|
73
|
+
await handle.doc()
|
|
74
|
+
assert.equal(handle.docSync().foo, "bar")
|
|
75
|
+
})
|
|
76
|
+
|
|
60
77
|
it("can find a document by url", () => {
|
|
61
78
|
const { repo } = setup()
|
|
62
79
|
const handle = repo.create<TestDoc>()
|
|
@@ -321,6 +338,27 @@ describe("Repo", () => {
|
|
|
321
338
|
repo.delete(handle.documentId)
|
|
322
339
|
}))
|
|
323
340
|
|
|
341
|
+
it("exports a document", async () => {
|
|
342
|
+
const { repo } = setup()
|
|
343
|
+
const handle = repo.create<TestDoc>()
|
|
344
|
+
handle.change(d => {
|
|
345
|
+
d.foo = "bar"
|
|
346
|
+
})
|
|
347
|
+
assert.equal(handle.isReady(), true)
|
|
348
|
+
|
|
349
|
+
const exported = await repo.export(handle.documentId)
|
|
350
|
+
const loaded = A.load(exported)
|
|
351
|
+
const doc = await handle.doc()
|
|
352
|
+
assert.deepEqual(doc, loaded)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it("rejects when exporting a document that does not exist", async () => {
|
|
356
|
+
const { repo } = setup()
|
|
357
|
+
assert.rejects(async () => {
|
|
358
|
+
await repo.export("foo" as AnyDocumentId)
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
324
362
|
it("storage state doesn't change across reloads when the document hasn't changed", async () => {
|
|
325
363
|
const storage = new DummyStorageAdapter()
|
|
326
364
|
|
|
@@ -396,6 +434,30 @@ describe("Repo", () => {
|
|
|
396
434
|
const storageKeyTypes = storageAdapter.keys().map(k => k.split(".")[1])
|
|
397
435
|
assert(storageKeyTypes.filter(k => k === "snapshot").length === 1)
|
|
398
436
|
})
|
|
437
|
+
|
|
438
|
+
it("can import an existing document", async () => {
|
|
439
|
+
const { repo } = setup()
|
|
440
|
+
const doc = A.init<TestDoc>()
|
|
441
|
+
const updatedDoc = A.change(doc, d => {
|
|
442
|
+
d.foo = "bar"
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
const saved = A.save(updatedDoc)
|
|
446
|
+
|
|
447
|
+
const handle = repo.import<TestDoc>(saved)
|
|
448
|
+
assert.equal(handle.isReady(), true)
|
|
449
|
+
const v = await handle.doc()
|
|
450
|
+
assert.equal(v?.foo, "bar")
|
|
451
|
+
|
|
452
|
+
expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it("throws an error if we try to import an invalid document", async () => {
|
|
456
|
+
const { repo } = setup()
|
|
457
|
+
expect(() => {
|
|
458
|
+
repo.import<TestDoc>(A.init<TestDoc> as unknown as Uint8Array)
|
|
459
|
+
}).toThrow()
|
|
460
|
+
})
|
|
399
461
|
})
|
|
400
462
|
|
|
401
463
|
describe("with peers (linear network)", async () => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EventEmitter } from "eventemitter3"
|
|
2
|
+
import { pause } from "../../src/helpers/pause.js"
|
|
3
|
+
|
|
4
|
+
export async function collectMessages({
|
|
5
|
+
emitter,
|
|
6
|
+
event,
|
|
7
|
+
until = pause(100),
|
|
8
|
+
}: {
|
|
9
|
+
emitter: EventEmitter
|
|
10
|
+
event: string
|
|
11
|
+
until?: Promise<unknown>
|
|
12
|
+
}): Promise<any[]> {
|
|
13
|
+
const messages = []
|
|
14
|
+
const listener = (message: unknown) => messages.push(message)
|
|
15
|
+
emitter.on(event, listener)
|
|
16
|
+
await until
|
|
17
|
+
emitter.off(event)
|
|
18
|
+
return messages
|
|
19
|
+
}
|
package/test/remoteHeads.test.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
|
|
2
1
|
import * as A from "@automerge/automerge/next"
|
|
3
2
|
import assert from "assert"
|
|
4
|
-
import { setTimeout } from "timers/promises"
|
|
5
3
|
import { describe, it } from "vitest"
|
|
4
|
+
import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/dist/index.js"
|
|
6
5
|
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
7
6
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
8
7
|
import {
|
|
@@ -12,7 +11,7 @@ import {
|
|
|
12
11
|
Repo,
|
|
13
12
|
} from "../src/index.js"
|
|
14
13
|
import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
|
|
15
|
-
import {
|
|
14
|
+
import { collectMessages } from "./helpers/collectMessages.js"
|
|
16
15
|
import { TestDoc } from "./types.js"
|
|
17
16
|
|
|
18
17
|
describe("DocHandle.remoteHeads", () => {
|
|
@@ -24,6 +23,7 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
24
23
|
peerId: "bob" as PeerId,
|
|
25
24
|
network: [],
|
|
26
25
|
storage: new DummyStorageAdapter(),
|
|
26
|
+
enableRemoteHeadsGossiping: true,
|
|
27
27
|
})
|
|
28
28
|
const bobStorageId = await bobRepo.storageId()
|
|
29
29
|
|
|
@@ -41,23 +41,26 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
41
41
|
|
|
42
42
|
describe("multi hop sync", () => {
|
|
43
43
|
async function setup() {
|
|
44
|
-
// setup topology:
|
|
45
|
-
const
|
|
46
|
-
peerId: "
|
|
44
|
+
// setup topology: alice -> service worker -> sync server <- service worker <- bob
|
|
45
|
+
const alice = new Repo({
|
|
46
|
+
peerId: "alice-tab-1" as PeerId,
|
|
47
47
|
network: [],
|
|
48
48
|
sharePolicy: async () => true,
|
|
49
|
+
enableRemoteHeadsGossiping: true,
|
|
49
50
|
})
|
|
50
|
-
const
|
|
51
|
-
peerId: "
|
|
51
|
+
const alice2 = new Repo({
|
|
52
|
+
peerId: "alice-tab-2" as PeerId,
|
|
52
53
|
network: [],
|
|
53
54
|
sharePolicy: async () => true,
|
|
55
|
+
enableRemoteHeadsGossiping: true,
|
|
54
56
|
})
|
|
55
|
-
const
|
|
56
|
-
peerId: "
|
|
57
|
+
const aliceServiceWorker = new Repo({
|
|
58
|
+
peerId: "alice-service-worker" as PeerId,
|
|
57
59
|
network: [],
|
|
58
60
|
sharePolicy: async peer => peer === "sync-server",
|
|
59
61
|
storage: new DummyStorageAdapter(),
|
|
60
62
|
isEphemeral: false,
|
|
63
|
+
enableRemoteHeadsGossiping: true,
|
|
61
64
|
})
|
|
62
65
|
const syncServer = new Repo({
|
|
63
66
|
peerId: "sync-server" as PeerId,
|
|
@@ -65,191 +68,209 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
65
68
|
isEphemeral: false,
|
|
66
69
|
sharePolicy: async () => false,
|
|
67
70
|
storage: new DummyStorageAdapter(),
|
|
71
|
+
enableRemoteHeadsGossiping: true,
|
|
68
72
|
})
|
|
69
|
-
const
|
|
70
|
-
peerId: "
|
|
73
|
+
const bobServiceWorker = new Repo({
|
|
74
|
+
peerId: "bob-service-worker" as PeerId,
|
|
71
75
|
network: [],
|
|
72
76
|
sharePolicy: async peer => peer === "sync-server",
|
|
73
77
|
isEphemeral: false,
|
|
74
78
|
storage: new DummyStorageAdapter(),
|
|
79
|
+
enableRemoteHeadsGossiping: true,
|
|
75
80
|
})
|
|
76
|
-
const
|
|
77
|
-
peerId: "
|
|
81
|
+
const bob = new Repo({
|
|
82
|
+
peerId: "bob-tab" as PeerId,
|
|
78
83
|
network: [],
|
|
79
84
|
sharePolicy: async () => true,
|
|
85
|
+
enableRemoteHeadsGossiping: true,
|
|
80
86
|
})
|
|
81
87
|
|
|
82
88
|
// connect them all up
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
await Promise.all([
|
|
90
|
+
connectRepos(alice, aliceServiceWorker),
|
|
91
|
+
connectRepos(alice2, aliceServiceWorker),
|
|
92
|
+
connectRepos(aliceServiceWorker, syncServer),
|
|
93
|
+
connectRepos(syncServer, bobServiceWorker),
|
|
94
|
+
connectRepos(bobServiceWorker, bob),
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
const alice1StorageId = await aliceServiceWorker.storageId()
|
|
98
|
+
const alice2StorageId = await aliceServiceWorker.storageId()
|
|
99
|
+
const aliceServiceWorkerStorageId = await aliceServiceWorker.storageId()
|
|
100
|
+
const syncServerStorageId = await syncServer.storageId()
|
|
101
|
+
const bobServiceWorkerStorageId = await bobServiceWorker.storageId()
|
|
102
|
+
const bobStorageId = await bobServiceWorker.storageId()
|
|
90
103
|
|
|
91
104
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
alice,
|
|
106
|
+
alice2,
|
|
107
|
+
aliceServiceWorker,
|
|
95
108
|
syncServer,
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
bobServiceWorker,
|
|
110
|
+
bob,
|
|
111
|
+
alice1StorageId,
|
|
112
|
+
alice2StorageId,
|
|
113
|
+
aliceServiceWorkerStorageId,
|
|
114
|
+
syncServerStorageId,
|
|
115
|
+
bobServiceWorkerStorageId,
|
|
116
|
+
bobStorageId,
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
it("should report remoteHeads for peers", async () => {
|
|
102
|
-
const {
|
|
121
|
+
const { bob, aliceServiceWorkerStorageId, aliceServiceWorker, alice } =
|
|
103
122
|
await setup()
|
|
104
123
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
await setTimeout(100)
|
|
124
|
+
// bob subscribes to alice's service worker's storageId
|
|
125
|
+
bob.subscribeToRemotes([aliceServiceWorkerStorageId])
|
|
109
126
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
127
|
+
// alice creates a doc
|
|
128
|
+
const aliceDoc = alice.create<TestDoc>()
|
|
129
|
+
aliceDoc.change(d => (d.foo = "bar"))
|
|
113
130
|
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
await
|
|
131
|
+
// bob waits for the document to arrive
|
|
132
|
+
const bobDoc = bob.find<TestDoc>(aliceDoc.url)
|
|
133
|
+
await bobDoc.whenReady()
|
|
117
134
|
|
|
118
|
-
//
|
|
119
|
-
const
|
|
120
|
-
await
|
|
135
|
+
// alice's service worker waits for the document to arrive
|
|
136
|
+
const aliceServiceWorkerDoc = aliceServiceWorker.find(aliceDoc.documentId)
|
|
137
|
+
await aliceServiceWorkerDoc.whenReady()
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
|
|
139
|
+
let aliceSeenByBobPromise = new Promise<DocHandleRemoteHeadsPayload>(
|
|
124
140
|
resolve => {
|
|
125
|
-
|
|
126
|
-
if (message.storageId ===
|
|
141
|
+
bobDoc.on("remote-heads", message => {
|
|
142
|
+
if (message.storageId === aliceServiceWorkerStorageId) {
|
|
127
143
|
resolve(message)
|
|
128
144
|
}
|
|
129
145
|
})
|
|
130
146
|
}
|
|
131
147
|
)
|
|
132
148
|
|
|
133
|
-
//
|
|
134
|
-
|
|
149
|
+
// bob makes a change
|
|
150
|
+
bobDoc.change(d => (d.foo = "baz"))
|
|
135
151
|
|
|
136
|
-
// wait for
|
|
137
|
-
const
|
|
152
|
+
// wait for alice's service worker to acknowledge the change
|
|
153
|
+
const { heads } = await aliceSeenByBobPromise
|
|
138
154
|
|
|
139
|
-
assert.deepStrictEqual(
|
|
140
|
-
leftSeenByRight.heads,
|
|
141
|
-
A.getHeads(leftServiceWorkerDoc.docSync())
|
|
142
|
-
)
|
|
155
|
+
assert.deepStrictEqual(heads, A.getHeads(aliceServiceWorkerDoc.docSync()))
|
|
143
156
|
})
|
|
144
157
|
|
|
145
158
|
it("should report remoteHeads only for documents the subscriber has open", async () => {
|
|
146
|
-
const {
|
|
147
|
-
|
|
148
|
-
// subscribe leftTab to storageId of rightServiceWorker
|
|
149
|
-
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
159
|
+
const { alice, bob, bobServiceWorkerStorageId } = await setup()
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
// alice subscribes to bob's service worker
|
|
162
|
+
alice.subscribeToRemotes([bobServiceWorkerStorageId])
|
|
152
163
|
|
|
153
|
-
//
|
|
154
|
-
const
|
|
155
|
-
|
|
164
|
+
// bob creates two docs
|
|
165
|
+
const bobDocA = bob.create<TestDoc>()
|
|
166
|
+
bobDocA.change(d => (d.foo = "A"))
|
|
156
167
|
|
|
157
|
-
const
|
|
158
|
-
|
|
168
|
+
const bobDocB = bob.create<TestDoc>()
|
|
169
|
+
bobDocB.change(d => (d.foo = "B"))
|
|
159
170
|
|
|
160
|
-
//
|
|
161
|
-
const
|
|
171
|
+
// alice opens doc A
|
|
172
|
+
const aliceDocA = alice.find<TestDoc>(bobDocA.url)
|
|
162
173
|
|
|
163
174
|
const remoteHeadsChangedMessages = (
|
|
164
|
-
await
|
|
175
|
+
await collectMessages({
|
|
176
|
+
emitter: alice.networkSubsystem,
|
|
177
|
+
event: "message",
|
|
178
|
+
until: aliceDocA.whenReady(),
|
|
179
|
+
})
|
|
165
180
|
).filter(({ type }) => type === "remote-heads-changed")
|
|
166
181
|
|
|
167
182
|
// we should only be notified of the head changes of doc A
|
|
168
|
-
assert
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
assert(
|
|
184
|
+
remoteHeadsChangedMessages.every(
|
|
185
|
+
d => d.documentId === aliceDocA.documentId
|
|
186
|
+
)
|
|
172
187
|
)
|
|
173
188
|
})
|
|
174
189
|
|
|
175
190
|
it("should report remote heads for doc on subscribe if peer already knows them", async () => {
|
|
176
|
-
const {
|
|
191
|
+
const { alice, alice2, bob, bobServiceWorkerStorageId } = await setup()
|
|
177
192
|
|
|
178
|
-
//
|
|
179
|
-
const
|
|
180
|
-
|
|
193
|
+
// bob creates 2 docs
|
|
194
|
+
const bobDocA = bob.create<TestDoc>()
|
|
195
|
+
bobDocA.change(d => (d.foo = "A"))
|
|
181
196
|
|
|
182
|
-
const
|
|
183
|
-
|
|
197
|
+
const bobDocB = bob.create<TestDoc>()
|
|
198
|
+
bobDocB.change(d => (d.foo = "B"))
|
|
184
199
|
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
const
|
|
200
|
+
// alice opens the docs
|
|
201
|
+
const _aliceDocA = alice.find<TestDoc>(bobDocA.url)
|
|
202
|
+
const _aliceDocB = alice.find<TestDoc>(bobDocB.url)
|
|
188
203
|
|
|
189
|
-
//
|
|
190
|
-
|
|
204
|
+
// alice subscribes to bob's service worker
|
|
205
|
+
alice.subscribeToRemotes([bobServiceWorkerStorageId])
|
|
191
206
|
|
|
192
|
-
|
|
207
|
+
// Now alice's service worker has the remote heads of bob's service worker for both doc A and
|
|
208
|
+
// doc B. If alice subscribes to bob's service worker, bob's service worker should send its
|
|
209
|
+
// stored remote heads immediately.
|
|
193
210
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// open doc and subscribe leftTab 2 to storageId of rightServiceWorker
|
|
198
|
-
const leftTab2DocA = leftTab2.find<TestDoc>(rightTabDocA.url)
|
|
199
|
-
leftTab2.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
211
|
+
// open doc and subscribe alice's second tab to bob's service worker
|
|
212
|
+
const alice2DocA = alice2.find<TestDoc>(bobDocA.url)
|
|
213
|
+
alice2.subscribeToRemotes([bobServiceWorkerStorageId])
|
|
200
214
|
|
|
201
215
|
const remoteHeadsChangedMessages = (
|
|
202
|
-
await
|
|
216
|
+
await collectMessages({
|
|
217
|
+
emitter: alice2.networkSubsystem,
|
|
218
|
+
event: "message",
|
|
219
|
+
until: alice2DocA.whenReady(),
|
|
220
|
+
})
|
|
203
221
|
).filter(({ type }) => type === "remote-heads-changed")
|
|
204
222
|
|
|
205
223
|
// we should only be notified of the head changes of doc A
|
|
206
|
-
assert.strictEqual(remoteHeadsChangedMessages.length,
|
|
207
|
-
assert
|
|
208
|
-
remoteHeadsChangedMessages
|
|
209
|
-
|
|
224
|
+
assert.strictEqual(remoteHeadsChangedMessages.length, 2)
|
|
225
|
+
assert(
|
|
226
|
+
remoteHeadsChangedMessages.every(
|
|
227
|
+
d => d.documentId === alice2DocA.documentId
|
|
228
|
+
)
|
|
210
229
|
)
|
|
211
230
|
})
|
|
212
231
|
|
|
213
232
|
it("should report remote heads for subscribed storage id once we open a new doc", async () => {
|
|
214
|
-
const {
|
|
215
|
-
|
|
216
|
-
// create 2 docs in right tab
|
|
217
|
-
const rightTabDocA = rightTab.create<TestDoc>()
|
|
218
|
-
rightTabDocA.change(d => (d.foo = "A"))
|
|
233
|
+
const { alice, bob, bobServiceWorkerStorageId } = await setup()
|
|
219
234
|
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
// bob creates 2 docs
|
|
236
|
+
const bobDocA = bob.create<TestDoc>()
|
|
237
|
+
bobDocA.change(d => (d.foo = "A"))
|
|
222
238
|
|
|
223
|
-
|
|
239
|
+
const bobDocB = bob.create<TestDoc>()
|
|
240
|
+
bobDocB.change(d => (d.foo = "B"))
|
|
224
241
|
|
|
225
|
-
//
|
|
226
|
-
|
|
242
|
+
// alice subscribes to bob's service worker
|
|
243
|
+
alice.subscribeToRemotes([bobServiceWorkerStorageId])
|
|
227
244
|
|
|
228
|
-
//
|
|
229
|
-
const
|
|
245
|
+
// alice opens doc A
|
|
246
|
+
const alice1DocA = alice.find<TestDoc>(bobDocA.url)
|
|
230
247
|
|
|
231
248
|
const remoteHeadsChangedMessages = (
|
|
232
|
-
await
|
|
249
|
+
await collectMessages({
|
|
250
|
+
emitter: alice.networkSubsystem,
|
|
251
|
+
event: "message",
|
|
252
|
+
until: alice1DocA.whenReady(),
|
|
253
|
+
})
|
|
233
254
|
).filter(({ type }) => type === "remote-heads-changed")
|
|
234
255
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
leftTab1DocA.documentId
|
|
256
|
+
assert.strictEqual(remoteHeadsChangedMessages.length, 2)
|
|
257
|
+
assert(
|
|
258
|
+
remoteHeadsChangedMessages.every(
|
|
259
|
+
d => d.documentId === alice1DocA.documentId
|
|
260
|
+
)
|
|
241
261
|
)
|
|
242
262
|
})
|
|
243
263
|
})
|
|
244
264
|
})
|
|
245
265
|
|
|
246
|
-
function connectRepos(
|
|
247
|
-
const { port1:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
266
|
+
async function connectRepos(a: Repo, b: Repo) {
|
|
267
|
+
const { port1: a2b, port2: b2a } = new MessageChannel()
|
|
268
|
+
const aAdapter = new MessageChannelNetworkAdapter(a2b)
|
|
269
|
+
const bAdapter = new MessageChannelNetworkAdapter(b2a)
|
|
270
|
+
a.networkSubsystem.addNetworkAdapter(aAdapter)
|
|
271
|
+
b.networkSubsystem.addNetworkAdapter(bAdapter)
|
|
272
|
+
await Promise.all([
|
|
273
|
+
eventPromise(a.networkSubsystem, "ready"),
|
|
274
|
+
eventPromise(b.networkSubsystem, "ready"),
|
|
275
|
+
])
|
|
255
276
|
}
|
package/.eslintrc
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"es2021": true
|
|
5
|
-
},
|
|
6
|
-
"extends": [
|
|
7
|
-
"eslint:recommended",
|
|
8
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
9
|
-
"plugin:@typescript-eslint/recommended"
|
|
10
|
-
],
|
|
11
|
-
"ignorePatterns": ["dist/**", "test/**", "node_modules/**"],
|
|
12
|
-
"parser": "@typescript-eslint/parser",
|
|
13
|
-
"plugins": ["@typescript-eslint"],
|
|
14
|
-
"parserOptions": {
|
|
15
|
-
"project": "./tsconfig.json",
|
|
16
|
-
"ecmaVersion": "latest",
|
|
17
|
-
"sourceType": "module"
|
|
18
|
-
},
|
|
19
|
-
"rules": {
|
|
20
|
-
"semi": ["error", "never"],
|
|
21
|
-
"import/extensions": 0,
|
|
22
|
-
"lines-between-class-members": 0,
|
|
23
|
-
"@typescript-eslint/no-floating-promises": "error",
|
|
24
|
-
"@typescript-eslint/no-empty-function": ["warn", { "allow": ["methods"] }],
|
|
25
|
-
"no-param-reassign": 0,
|
|
26
|
-
"no-use-before-define": 0
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "eventemitter3"
|
|
2
|
-
import { pause } from "../../src/helpers/pause.js"
|
|
3
|
-
|
|
4
|
-
export async function waitForMessages(
|
|
5
|
-
emitter: EventEmitter,
|
|
6
|
-
event: string,
|
|
7
|
-
timeout: number = 100
|
|
8
|
-
): Promise<any[]> {
|
|
9
|
-
const messages = []
|
|
10
|
-
|
|
11
|
-
const onEvent = message => {
|
|
12
|
-
messages.push(message)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
emitter.on(event, onEvent)
|
|
16
|
-
|
|
17
|
-
await pause(timeout)
|
|
18
|
-
|
|
19
|
-
emitter.off(event)
|
|
20
|
-
|
|
21
|
-
return messages
|
|
22
|
-
}
|