@automerge/automerge-repo 1.1.0-alpha.1 → 1.1.0-alpha.13

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.
Files changed (58) hide show
  1. package/README.md +12 -7
  2. package/dist/AutomergeUrl.js +2 -2
  3. package/dist/RemoteHeadsSubscriptions.d.ts +1 -0
  4. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
  5. package/dist/RemoteHeadsSubscriptions.js +76 -16
  6. package/dist/Repo.d.ts +23 -10
  7. package/dist/Repo.d.ts.map +1 -1
  8. package/dist/Repo.js +103 -54
  9. package/dist/helpers/debounce.js +1 -1
  10. package/dist/helpers/pause.d.ts.map +1 -1
  11. package/dist/helpers/pause.js +2 -0
  12. package/dist/helpers/throttle.js +1 -1
  13. package/dist/helpers/withTimeout.d.ts.map +1 -1
  14. package/dist/helpers/withTimeout.js +2 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/network/NetworkAdapter.d.ts +14 -7
  19. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  20. package/dist/network/NetworkAdapter.js +3 -3
  21. package/dist/network/NetworkSubsystem.d.ts +4 -8
  22. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  23. package/dist/network/NetworkSubsystem.js +12 -13
  24. package/dist/network/messages.d.ts +48 -38
  25. package/dist/network/messages.d.ts.map +1 -1
  26. package/dist/network/messages.js +7 -9
  27. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  28. package/dist/storage/StorageSubsystem.js +7 -2
  29. package/dist/storage/keyHash.d.ts.map +1 -1
  30. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  31. package/dist/synchronizer/CollectionSynchronizer.js +5 -3
  32. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  33. package/dist/synchronizer/DocSynchronizer.js +20 -8
  34. package/dist/synchronizer/Synchronizer.d.ts +12 -3
  35. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  36. package/package.json +6 -6
  37. package/src/AutomergeUrl.ts +2 -2
  38. package/src/RemoteHeadsSubscriptions.ts +85 -16
  39. package/src/Repo.ts +131 -68
  40. package/src/helpers/debounce.ts +1 -1
  41. package/src/helpers/pause.ts +4 -0
  42. package/src/helpers/throttle.ts +1 -1
  43. package/src/helpers/withTimeout.ts +2 -0
  44. package/src/index.ts +2 -1
  45. package/src/network/NetworkAdapter.ts +18 -12
  46. package/src/network/NetworkSubsystem.ts +23 -24
  47. package/src/network/messages.ts +77 -68
  48. package/src/storage/StorageSubsystem.ts +7 -2
  49. package/src/storage/keyHash.ts +2 -0
  50. package/src/synchronizer/CollectionSynchronizer.ts +7 -4
  51. package/src/synchronizer/DocSynchronizer.ts +27 -15
  52. package/src/synchronizer/Synchronizer.ts +13 -3
  53. package/test/RemoteHeadsSubscriptions.test.ts +34 -24
  54. package/test/Repo.test.ts +57 -2
  55. package/test/StorageSubsystem.test.ts +1 -1
  56. package/test/helpers/waitForMessages.ts +22 -0
  57. package/test/remoteHeads.test.ts +197 -72
  58. package/.eslintrc +0 -28
@@ -1,15 +1,14 @@
1
1
  import * as A from "@automerge/automerge"
2
2
  import assert from "assert"
3
3
  import { describe, it } from "vitest"
4
+ import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
4
5
  import { RemoteHeadsSubscriptions } from "../src/RemoteHeadsSubscriptions.js"
5
6
  import { PeerId, StorageId } from "../src/index.js"
6
- import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
7
- import { pause } from "../src/helpers/pause.js"
8
- import { EventEmitter } from "eventemitter3"
9
7
  import {
10
8
  RemoteHeadsChanged,
11
9
  RemoteSubscriptionControlMessage,
12
10
  } from "../src/network/messages.js"
11
+ import { waitForMessages } from "./helpers/waitForMessages.js"
13
12
 
14
13
  describe("RepoHeadsSubscriptions", () => {
15
14
  const storageA = "remote-a" as StorageId
@@ -224,6 +223,8 @@ describe("RepoHeadsSubscriptions", () => {
224
223
  remoteHeadsSubscriptions,
225
224
  "notify-remote-heads"
226
225
  )
226
+ remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docA)
227
+ remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docC)
227
228
 
228
229
  // change message for docA in storageB
229
230
  remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
@@ -260,7 +261,33 @@ describe("RepoHeadsSubscriptions", () => {
260
261
  assert.strictEqual(messages.length, 0)
261
262
  })
262
263
 
263
- it("should ignore sync states with an older timestamp", async () => {
264
+ it("should not send remote heads for docs that the peer is not subscribed to", async () => {
265
+ const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
266
+ remoteHeadsSubscriptions.subscribeToRemotes([storageB])
267
+
268
+ // subscribe peer c to storage b
269
+ remoteHeadsSubscriptions.handleControlMessage(subscribePeerCToStorageB)
270
+ const messagesAfterSubscribePromise = waitForMessages(
271
+ remoteHeadsSubscriptions,
272
+ "notify-remote-heads"
273
+ )
274
+
275
+ // change message for docA in storageB
276
+ remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
277
+
278
+ // change heads directly
279
+ remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
280
+ docC,
281
+ storageB,
282
+ []
283
+ )
284
+
285
+ // expect peer c to be notified both changes
286
+ let messages = await messagesAfterSubscribePromise
287
+ assert.strictEqual(messages.length, 0)
288
+ })
289
+
290
+ it("should only notify of sync states with a more recent timestamp", async () => {
264
291
  const remoteHeadsSubscription = new RemoteHeadsSubscriptions()
265
292
 
266
293
  const messagesPromise = waitForMessages(
@@ -271,6 +298,9 @@ describe("RepoHeadsSubscriptions", () => {
271
298
  remoteHeadsSubscription.subscribeToRemotes([storageB])
272
299
  remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
273
300
 
301
+ // send same message
302
+ remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
303
+
274
304
  // send message with old heads
275
305
  remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB)
276
306
 
@@ -321,23 +351,3 @@ describe("RepoHeadsSubscriptions", () => {
321
351
  assert.deepStrictEqual(messages[2].peers, [])
322
352
  })
323
353
  })
324
-
325
- async function waitForMessages(
326
- emitter: EventEmitter,
327
- event: string,
328
- timeout: number = 100
329
- ): Promise<any[]> {
330
- const messages = []
331
-
332
- const onEvent = message => {
333
- messages.push(message)
334
- }
335
-
336
- emitter.on(event, onEvent)
337
-
338
- await pause(timeout)
339
-
340
- emitter.off(event)
341
-
342
- return messages
343
- }
package/test/Repo.test.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { next as A } from "@automerge/automerge"
2
- import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
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()
@@ -321,6 +331,27 @@ describe("Repo", () => {
321
331
  repo.delete(handle.documentId)
322
332
  }))
323
333
 
334
+ it("exports a document", async () => {
335
+ const { repo } = setup()
336
+ const handle = repo.create<TestDoc>()
337
+ handle.change(d => {
338
+ d.foo = "bar"
339
+ })
340
+ assert.equal(handle.isReady(), true)
341
+
342
+ const exported = await repo.export(handle.documentId)
343
+ const loaded = A.load(exported)
344
+ const doc = await handle.doc()
345
+ assert.deepEqual(doc, loaded)
346
+ })
347
+
348
+ it("rejects when exporting a document that does not exist", async () => {
349
+ const { repo } = setup()
350
+ assert.rejects(async () => {
351
+ await repo.export("foo" as AnyDocumentId)
352
+ })
353
+ })
354
+
324
355
  it("storage state doesn't change across reloads when the document hasn't changed", async () => {
325
356
  const storage = new DummyStorageAdapter()
326
357
 
@@ -396,6 +427,30 @@ describe("Repo", () => {
396
427
  const storageKeyTypes = storageAdapter.keys().map(k => k.split(".")[1])
397
428
  assert(storageKeyTypes.filter(k => k === "snapshot").length === 1)
398
429
  })
430
+
431
+ it("can import an existing document", async () => {
432
+ const { repo } = setup()
433
+ const doc = A.init<TestDoc>()
434
+ const updatedDoc = A.change(doc, d => {
435
+ d.foo = "bar"
436
+ })
437
+
438
+ const saved = A.save(updatedDoc)
439
+
440
+ const handle = repo.import<TestDoc>(saved)
441
+ assert.equal(handle.isReady(), true)
442
+ const v = await handle.doc()
443
+ assert.equal(v?.foo, "bar")
444
+
445
+ expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
446
+ })
447
+
448
+ it("throws an error if we try to import an invalid document", async () => {
449
+ const { repo } = setup()
450
+ expect(() => {
451
+ repo.import<TestDoc>(A.init<TestDoc> as unknown as Uint8Array)
452
+ }).toThrow()
453
+ })
399
454
  })
400
455
 
401
456
  describe("with peers (linear network)", async () => {
@@ -1,4 +1,4 @@
1
- import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs"
1
+ import { NodeFSStorageAdapter } from "../../automerge-repo-storage-nodefs/src/index.js"
2
2
  import * as A from "@automerge/automerge/next"
3
3
  import assert from "assert"
4
4
  import fs from "fs"
@@ -0,0 +1,22 @@
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
+ }
@@ -1,20 +1,19 @@
1
+ import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/dist/index.js"
1
2
  import * as A from "@automerge/automerge/next"
2
3
  import assert from "assert"
3
- import { decode } from "cbor-x"
4
+ import { setTimeout } from "timers/promises"
4
5
  import { describe, it } from "vitest"
5
6
  import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
6
7
  import { eventPromise } from "../src/helpers/eventPromise.js"
7
- import { pause } from "../src/helpers/pause.js"
8
8
  import {
9
9
  DocHandle,
10
10
  DocHandleRemoteHeadsPayload,
11
11
  PeerId,
12
12
  Repo,
13
13
  } from "../src/index.js"
14
- import { TestDoc } from "./types.js"
15
- import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
16
- import { setTimeout } from "timers/promises"
17
14
  import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
15
+ import { waitForMessages } from "./helpers/waitForMessages.js"
16
+ import { TestDoc } from "./types.js"
18
17
 
19
18
  describe("DocHandle.remoteHeads", () => {
20
19
  const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId
@@ -25,6 +24,7 @@ describe("DocHandle.remoteHeads", () => {
25
24
  peerId: "bob" as PeerId,
26
25
  network: [],
27
26
  storage: new DummyStorageAdapter(),
27
+ enableRemoteHeadsGossiping: true,
28
28
  })
29
29
  const bobStorageId = await bobRepo.storageId()
30
30
 
@@ -40,86 +40,211 @@ describe("DocHandle.remoteHeads", () => {
40
40
  assert.deepStrictEqual(handle.getRemoteHeads(bobStorageId), [])
41
41
  })
42
42
 
43
- it("should report remoteHeads for peers who are several hops away", async () => {
44
- // replicates a tab -> service worker -> sync server <- service worker <- tab scenario
45
- const leftTab = new Repo({
46
- peerId: "left-tab" as PeerId,
47
- network: [],
48
- sharePolicy: async () => true,
49
- })
50
- const leftServiceWorker = new Repo({
51
- peerId: "left-service-worker" as PeerId,
52
- network: [],
53
- sharePolicy: async peer => peer === "sync-server",
54
- storage: new DummyStorageAdapter(),
55
- isEphemeral: false,
56
- })
57
- const syncServer = new Repo({
58
- peerId: "sync-server" as PeerId,
59
- network: [],
60
- isEphemeral: false,
61
- sharePolicy: async () => false,
62
- storage: new DummyStorageAdapter(),
63
- })
64
- const rightServiceWorker = new Repo({
65
- peerId: "right-service-worker" as PeerId,
66
- network: [],
67
- sharePolicy: async peer => peer === "sync-server",
68
- isEphemeral: false,
69
- storage: new DummyStorageAdapter(),
43
+ describe("multi hop sync", () => {
44
+ async function setup() {
45
+ // setup topology: tab -> service worker -> sync server <- service worker <- tab
46
+ const leftTab1 = new Repo({
47
+ peerId: "left-tab-1" as PeerId,
48
+ network: [],
49
+ sharePolicy: async () => true,
50
+ enableRemoteHeadsGossiping: true,
51
+ })
52
+ const leftTab2 = new Repo({
53
+ peerId: "left-tab-2" as PeerId,
54
+ network: [],
55
+ sharePolicy: async () => true,
56
+ enableRemoteHeadsGossiping: true,
57
+ })
58
+ const leftServiceWorker = new Repo({
59
+ peerId: "left-service-worker" as PeerId,
60
+ network: [],
61
+ sharePolicy: async peer => peer === "sync-server",
62
+ storage: new DummyStorageAdapter(),
63
+ isEphemeral: false,
64
+ enableRemoteHeadsGossiping: true,
65
+ })
66
+ const syncServer = new Repo({
67
+ peerId: "sync-server" as PeerId,
68
+ network: [],
69
+ isEphemeral: false,
70
+ sharePolicy: async () => false,
71
+ storage: new DummyStorageAdapter(),
72
+ enableRemoteHeadsGossiping: true,
73
+ })
74
+ const rightServiceWorker = new Repo({
75
+ peerId: "right-service-worker" as PeerId,
76
+ network: [],
77
+ sharePolicy: async peer => peer === "sync-server",
78
+ isEphemeral: false,
79
+ storage: new DummyStorageAdapter(),
80
+ enableRemoteHeadsGossiping: true,
81
+ })
82
+ const rightTab = new Repo({
83
+ peerId: "right-tab" as PeerId,
84
+ network: [],
85
+ sharePolicy: async () => true,
86
+ enableRemoteHeadsGossiping: true,
87
+ })
88
+
89
+ // connect them all up
90
+ connectRepos(leftTab1, leftServiceWorker)
91
+ connectRepos(leftTab2, leftServiceWorker)
92
+ connectRepos(leftServiceWorker, syncServer)
93
+ connectRepos(syncServer, rightServiceWorker)
94
+ connectRepos(rightServiceWorker, rightTab)
95
+
96
+ await setTimeout(100)
97
+
98
+ return {
99
+ leftTab1,
100
+ leftTab2,
101
+ leftServiceWorker,
102
+ syncServer,
103
+ rightServiceWorker,
104
+ rightTab,
105
+ }
106
+ }
107
+
108
+ it("should report remoteHeads for peers", async () => {
109
+ const { rightTab, rightServiceWorker, leftServiceWorker, leftTab1 } =
110
+ await setup()
111
+
112
+ // subscribe to the left service worker storage ID on the right tab
113
+ rightTab.subscribeToRemotes([await leftServiceWorker.storageId()!])
114
+
115
+ await setTimeout(100)
116
+
117
+ // create a doc in the left tab
118
+ const leftTabDoc = leftTab1.create<TestDoc>()
119
+ leftTabDoc.change(d => (d.foo = "bar"))
120
+
121
+ // wait for the document to arrive on the right tab
122
+ const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
123
+ await rightTabDoc.whenReady()
124
+
125
+ // wait for the document to arrive in the left service worker
126
+ const leftServiceWorkerDoc = leftServiceWorker.find(leftTabDoc.documentId)
127
+ await leftServiceWorkerDoc.whenReady()
128
+
129
+ const leftServiceWorkerStorageId = await leftServiceWorker.storageId()
130
+ let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
131
+ resolve => {
132
+ rightTabDoc.on("remote-heads", message => {
133
+ if (message.storageId === leftServiceWorkerStorageId) {
134
+ resolve(message)
135
+ }
136
+ })
137
+ }
138
+ )
139
+
140
+ // make a change on the right
141
+ rightTabDoc.change(d => (d.foo = "baz"))
142
+
143
+ // wait for the change to be acknolwedged by the left
144
+ const leftSeenByRight = await leftSeenByRightPromise
145
+
146
+ assert.deepStrictEqual(
147
+ leftSeenByRight.heads,
148
+ A.getHeads(leftServiceWorkerDoc.docSync())
149
+ )
70
150
  })
71
- const rightTab = new Repo({
72
- peerId: "right-tab" as PeerId,
73
- network: [],
74
- sharePolicy: async () => true,
151
+
152
+ it("should report remoteHeads only for documents the subscriber has open", async () => {
153
+ const { leftTab1, rightTab, rightServiceWorker } = await setup()
154
+
155
+ // subscribe leftTab to storageId of rightServiceWorker
156
+ leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
157
+
158
+ await setTimeout(100)
159
+
160
+ // create 2 docs in right tab
161
+ const rightTabDocA = rightTab.create<TestDoc>()
162
+ rightTabDocA.change(d => (d.foo = "A"))
163
+
164
+ const rightTabDocB = rightTab.create<TestDoc>()
165
+ rightTabDocB.change(d => (d.foo = "B"))
166
+
167
+ // open doc b in left tab 1
168
+ const leftTabDocA = leftTab1.find<TestDoc>(rightTabDocA.url)
169
+
170
+ const remoteHeadsChangedMessages = (
171
+ await waitForMessages(leftTab1.networkSubsystem, "message")
172
+ ).filter(({ type }) => type === "remote-heads-changed")
173
+
174
+ // we should only be notified of the head changes of doc A
175
+ const docIds = remoteHeadsChangedMessages.map(d => d.documentId)
176
+ const uniqueDocIds = [...new Set(docIds)]
177
+ assert.deepStrictEqual(uniqueDocIds, [leftTabDocA.documentId])
75
178
  })
76
179
 
77
- // connect them all up
78
- connectRepos(leftTab, leftServiceWorker)
79
- connectRepos(leftServiceWorker, syncServer)
80
- connectRepos(syncServer, rightServiceWorker)
81
- connectRepos(rightServiceWorker, rightTab)
180
+ it("should report remote heads for doc on subscribe if peer already knows them", async () => {
181
+ const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
82
182
 
83
- await setTimeout(100)
183
+ // create 2 docs in right tab
184
+ const rightTabDocA = rightTab.create<TestDoc>()
185
+ rightTabDocA.change(d => (d.foo = "A"))
84
186
 
85
- // subscribe to the left service worker storage ID on the right tab
86
- rightTab.subscribeToRemotes([await leftServiceWorker.storageId()!])
187
+ const rightTabDocB = rightTab.create<TestDoc>()
188
+ rightTabDocB.change(d => (d.foo = "B"))
87
189
 
88
- await setTimeout(100)
190
+ // open docs in left tab 1
191
+ const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
192
+ const leftTab1DocB = leftTab1.find<TestDoc>(rightTabDocB.url)
89
193
 
90
- // create a doc in the left tab
91
- const leftTabDoc = leftTab.create<TestDoc>()
92
- leftTabDoc.change(d => (d.foo = "bar"))
194
+ // subscribe leftTab 1 to storageId of rightServiceWorker
195
+ leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
93
196
 
94
- // wait for the document to arrive on the right tab
95
- const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
96
- await rightTabDoc.whenReady()
197
+ await setTimeout(200)
97
198
 
98
- // wait for the document to arrive in the left service worker
99
- const leftServiceWorkerDoc = leftServiceWorker.find(leftTabDoc.documentId)
100
- await leftServiceWorkerDoc.whenReady()
199
+ // now the left service worker has the remote heads of the right service worker for both doc A and doc B
200
+ // if we subscribe from left tab 1 the left service workers should send it's stored remote heads immediately
101
201
 
102
- const leftServiceWorkerStorageId = await leftServiceWorker.storageId()
103
- let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
104
- resolve => {
105
- rightTabDoc.on("remote-heads", message => {
106
- if (message.storageId === leftServiceWorkerStorageId) {
107
- resolve(message)
108
- }
109
- })
110
- }
111
- )
202
+ // open doc and subscribe leftTab 2 to storageId of rightServiceWorker
203
+ const leftTab2DocA = leftTab2.find<TestDoc>(rightTabDocA.url)
204
+ leftTab2.subscribeToRemotes([await rightServiceWorker.storageId()!])
205
+
206
+ const remoteHeadsChangedMessages = (
207
+ await waitForMessages(leftTab2.networkSubsystem, "message")
208
+ ).filter(({ type }) => type === "remote-heads-changed")
209
+
210
+ // we should only be notified of the head changes of doc A
211
+ assert.strictEqual(remoteHeadsChangedMessages.length, 1)
212
+ assert.strictEqual(
213
+ remoteHeadsChangedMessages[0].documentId,
214
+ leftTab1DocA.documentId
215
+ )
216
+ })
217
+
218
+ it("should report remote heads for subscribed storage id once we open a new doc", async () => {
219
+ const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
112
220
 
113
- // make a change on the right
114
- rightTabDoc.change(d => (d.foo = "baz"))
221
+ // create 2 docs in right tab
222
+ const rightTabDocA = rightTab.create<TestDoc>()
223
+ rightTabDocA.change(d => (d.foo = "A"))
115
224
 
116
- // wait for the change to be acknolwedged by the left
117
- const leftSeenByRight = await leftSeenByRightPromise
225
+ const rightTabDocB = rightTab.create<TestDoc>()
226
+ rightTabDocB.change(d => (d.foo = "B"))
118
227
 
119
- assert.deepStrictEqual(
120
- leftSeenByRight.heads,
121
- A.getHeads(leftServiceWorkerDoc.docSync())
122
- )
228
+ await setTimeout(200)
229
+
230
+ // subscribe leftTab 1 to storageId of rightServiceWorker
231
+ leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
232
+
233
+ // in leftTab 1 open doc A
234
+ const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
235
+
236
+ const remoteHeadsChangedMessages = (
237
+ await waitForMessages(leftTab1.networkSubsystem, "message")
238
+ ).filter(({ type }) => type === "remote-heads-changed")
239
+
240
+ // console.log(JSON.stringify(remoteHeadsChangedMessages, null, 2))
241
+
242
+ assert.strictEqual(remoteHeadsChangedMessages.length, 1)
243
+ assert.strictEqual(
244
+ remoteHeadsChangedMessages[0].documentId,
245
+ leftTab1DocA.documentId
246
+ )
247
+ })
123
248
  })
124
249
  })
125
250
 
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
- }