@automerge/automerge-repo 2.0.0-collectionsync-alpha.1 → 2.0.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.
Files changed (203) hide show
  1. package/README.md +8 -8
  2. package/dist/AutomergeUrl.d.ts +17 -5
  3. package/dist/AutomergeUrl.d.ts.map +1 -1
  4. package/dist/AutomergeUrl.js +71 -24
  5. package/dist/DocHandle.d.ts +33 -41
  6. package/dist/DocHandle.d.ts.map +1 -1
  7. package/dist/DocHandle.js +105 -66
  8. package/dist/FindProgress.d.ts +30 -0
  9. package/dist/FindProgress.d.ts.map +1 -0
  10. package/dist/FindProgress.js +1 -0
  11. package/dist/RemoteHeadsSubscriptions.d.ts +4 -5
  12. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
  13. package/dist/RemoteHeadsSubscriptions.js +4 -1
  14. package/dist/Repo.d.ts +24 -5
  15. package/dist/Repo.d.ts.map +1 -1
  16. package/dist/Repo.js +355 -169
  17. package/dist/helpers/abortable.d.ts +36 -0
  18. package/dist/helpers/abortable.d.ts.map +1 -0
  19. package/dist/helpers/abortable.js +47 -0
  20. package/dist/helpers/arraysAreEqual.d.ts.map +1 -1
  21. package/dist/helpers/bufferFromHex.d.ts +3 -0
  22. package/dist/helpers/bufferFromHex.d.ts.map +1 -0
  23. package/dist/helpers/bufferFromHex.js +13 -0
  24. package/dist/helpers/debounce.d.ts.map +1 -1
  25. package/dist/helpers/eventPromise.d.ts.map +1 -1
  26. package/dist/helpers/headsAreSame.d.ts +2 -2
  27. package/dist/helpers/headsAreSame.d.ts.map +1 -1
  28. package/dist/helpers/mergeArrays.d.ts +1 -1
  29. package/dist/helpers/mergeArrays.d.ts.map +1 -1
  30. package/dist/helpers/pause.d.ts.map +1 -1
  31. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  32. package/dist/helpers/tests/network-adapter-tests.js +13 -13
  33. package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -1
  34. package/dist/helpers/tests/storage-adapter-tests.js +6 -9
  35. package/dist/helpers/throttle.d.ts.map +1 -1
  36. package/dist/helpers/withTimeout.d.ts.map +1 -1
  37. package/dist/index.d.ts +35 -7
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +37 -6
  40. package/dist/network/NetworkSubsystem.d.ts +0 -1
  41. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  42. package/dist/network/NetworkSubsystem.js +0 -3
  43. package/dist/network/messages.d.ts +1 -7
  44. package/dist/network/messages.d.ts.map +1 -1
  45. package/dist/network/messages.js +1 -2
  46. package/dist/storage/StorageAdapter.d.ts +0 -9
  47. package/dist/storage/StorageAdapter.d.ts.map +1 -1
  48. package/dist/storage/StorageAdapter.js +0 -33
  49. package/dist/storage/StorageSubsystem.d.ts +6 -2
  50. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  51. package/dist/storage/StorageSubsystem.js +131 -37
  52. package/dist/storage/keyHash.d.ts +1 -1
  53. package/dist/storage/keyHash.d.ts.map +1 -1
  54. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -4
  55. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  56. package/dist/synchronizer/CollectionSynchronizer.js +32 -26
  57. package/dist/synchronizer/DocSynchronizer.d.ts +8 -8
  58. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  59. package/dist/synchronizer/DocSynchronizer.js +205 -79
  60. package/dist/types.d.ts +4 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/fuzz/fuzz.ts +3 -3
  63. package/package.json +4 -5
  64. package/src/AutomergeUrl.ts +101 -26
  65. package/src/DocHandle.ts +158 -77
  66. package/src/FindProgress.ts +48 -0
  67. package/src/RemoteHeadsSubscriptions.ts +11 -9
  68. package/src/Repo.ts +465 -180
  69. package/src/helpers/abortable.ts +62 -0
  70. package/src/helpers/bufferFromHex.ts +14 -0
  71. package/src/helpers/headsAreSame.ts +2 -2
  72. package/src/helpers/tests/network-adapter-tests.ts +14 -13
  73. package/src/helpers/tests/storage-adapter-tests.ts +13 -24
  74. package/src/index.ts +57 -38
  75. package/src/network/NetworkSubsystem.ts +0 -4
  76. package/src/network/messages.ts +2 -11
  77. package/src/storage/StorageAdapter.ts +0 -42
  78. package/src/storage/StorageSubsystem.ts +155 -45
  79. package/src/storage/keyHash.ts +1 -1
  80. package/src/synchronizer/CollectionSynchronizer.ts +42 -29
  81. package/src/synchronizer/DocSynchronizer.ts +263 -89
  82. package/src/types.ts +4 -1
  83. package/test/AutomergeUrl.test.ts +130 -0
  84. package/test/CollectionSynchronizer.test.ts +6 -8
  85. package/test/DocHandle.test.ts +161 -77
  86. package/test/DocSynchronizer.test.ts +11 -9
  87. package/test/RemoteHeadsSubscriptions.test.ts +1 -1
  88. package/test/Repo.test.ts +406 -341
  89. package/test/StorageSubsystem.test.ts +95 -20
  90. package/test/remoteHeads.test.ts +28 -13
  91. package/dist/CollectionHandle.d.ts +0 -14
  92. package/dist/CollectionHandle.d.ts.map +0 -1
  93. package/dist/CollectionHandle.js +0 -37
  94. package/dist/DocUrl.d.ts +0 -47
  95. package/dist/DocUrl.d.ts.map +0 -1
  96. package/dist/DocUrl.js +0 -72
  97. package/dist/EphemeralData.d.ts +0 -20
  98. package/dist/EphemeralData.d.ts.map +0 -1
  99. package/dist/EphemeralData.js +0 -1
  100. package/dist/ferigan.d.ts +0 -51
  101. package/dist/ferigan.d.ts.map +0 -1
  102. package/dist/ferigan.js +0 -98
  103. package/dist/src/DocHandle.d.ts +0 -182
  104. package/dist/src/DocHandle.d.ts.map +0 -1
  105. package/dist/src/DocHandle.js +0 -405
  106. package/dist/src/DocUrl.d.ts +0 -49
  107. package/dist/src/DocUrl.d.ts.map +0 -1
  108. package/dist/src/DocUrl.js +0 -72
  109. package/dist/src/EphemeralData.d.ts +0 -19
  110. package/dist/src/EphemeralData.d.ts.map +0 -1
  111. package/dist/src/EphemeralData.js +0 -1
  112. package/dist/src/Repo.d.ts +0 -74
  113. package/dist/src/Repo.d.ts.map +0 -1
  114. package/dist/src/Repo.js +0 -208
  115. package/dist/src/helpers/arraysAreEqual.d.ts +0 -2
  116. package/dist/src/helpers/arraysAreEqual.d.ts.map +0 -1
  117. package/dist/src/helpers/arraysAreEqual.js +0 -2
  118. package/dist/src/helpers/cbor.d.ts +0 -4
  119. package/dist/src/helpers/cbor.d.ts.map +0 -1
  120. package/dist/src/helpers/cbor.js +0 -8
  121. package/dist/src/helpers/eventPromise.d.ts +0 -11
  122. package/dist/src/helpers/eventPromise.d.ts.map +0 -1
  123. package/dist/src/helpers/eventPromise.js +0 -7
  124. package/dist/src/helpers/headsAreSame.d.ts +0 -2
  125. package/dist/src/helpers/headsAreSame.d.ts.map +0 -1
  126. package/dist/src/helpers/headsAreSame.js +0 -4
  127. package/dist/src/helpers/mergeArrays.d.ts +0 -2
  128. package/dist/src/helpers/mergeArrays.d.ts.map +0 -1
  129. package/dist/src/helpers/mergeArrays.js +0 -15
  130. package/dist/src/helpers/pause.d.ts +0 -6
  131. package/dist/src/helpers/pause.d.ts.map +0 -1
  132. package/dist/src/helpers/pause.js +0 -10
  133. package/dist/src/helpers/tests/network-adapter-tests.d.ts +0 -21
  134. package/dist/src/helpers/tests/network-adapter-tests.d.ts.map +0 -1
  135. package/dist/src/helpers/tests/network-adapter-tests.js +0 -122
  136. package/dist/src/helpers/withTimeout.d.ts +0 -12
  137. package/dist/src/helpers/withTimeout.d.ts.map +0 -1
  138. package/dist/src/helpers/withTimeout.js +0 -24
  139. package/dist/src/index.d.ts +0 -53
  140. package/dist/src/index.d.ts.map +0 -1
  141. package/dist/src/index.js +0 -40
  142. package/dist/src/network/NetworkAdapter.d.ts +0 -26
  143. package/dist/src/network/NetworkAdapter.d.ts.map +0 -1
  144. package/dist/src/network/NetworkAdapter.js +0 -4
  145. package/dist/src/network/NetworkSubsystem.d.ts +0 -23
  146. package/dist/src/network/NetworkSubsystem.d.ts.map +0 -1
  147. package/dist/src/network/NetworkSubsystem.js +0 -120
  148. package/dist/src/network/messages.d.ts +0 -85
  149. package/dist/src/network/messages.d.ts.map +0 -1
  150. package/dist/src/network/messages.js +0 -23
  151. package/dist/src/storage/StorageAdapter.d.ts +0 -14
  152. package/dist/src/storage/StorageAdapter.d.ts.map +0 -1
  153. package/dist/src/storage/StorageAdapter.js +0 -1
  154. package/dist/src/storage/StorageSubsystem.d.ts +0 -12
  155. package/dist/src/storage/StorageSubsystem.d.ts.map +0 -1
  156. package/dist/src/storage/StorageSubsystem.js +0 -145
  157. package/dist/src/synchronizer/CollectionSynchronizer.d.ts +0 -25
  158. package/dist/src/synchronizer/CollectionSynchronizer.d.ts.map +0 -1
  159. package/dist/src/synchronizer/CollectionSynchronizer.js +0 -106
  160. package/dist/src/synchronizer/DocSynchronizer.d.ts +0 -29
  161. package/dist/src/synchronizer/DocSynchronizer.d.ts.map +0 -1
  162. package/dist/src/synchronizer/DocSynchronizer.js +0 -263
  163. package/dist/src/synchronizer/Synchronizer.d.ts +0 -9
  164. package/dist/src/synchronizer/Synchronizer.d.ts.map +0 -1
  165. package/dist/src/synchronizer/Synchronizer.js +0 -2
  166. package/dist/src/types.d.ts +0 -16
  167. package/dist/src/types.d.ts.map +0 -1
  168. package/dist/src/types.js +0 -1
  169. package/dist/test/CollectionSynchronizer.test.d.ts +0 -2
  170. package/dist/test/CollectionSynchronizer.test.d.ts.map +0 -1
  171. package/dist/test/CollectionSynchronizer.test.js +0 -57
  172. package/dist/test/DocHandle.test.d.ts +0 -2
  173. package/dist/test/DocHandle.test.d.ts.map +0 -1
  174. package/dist/test/DocHandle.test.js +0 -238
  175. package/dist/test/DocSynchronizer.test.d.ts +0 -2
  176. package/dist/test/DocSynchronizer.test.d.ts.map +0 -1
  177. package/dist/test/DocSynchronizer.test.js +0 -111
  178. package/dist/test/Network.test.d.ts +0 -2
  179. package/dist/test/Network.test.d.ts.map +0 -1
  180. package/dist/test/Network.test.js +0 -11
  181. package/dist/test/Repo.test.d.ts +0 -2
  182. package/dist/test/Repo.test.d.ts.map +0 -1
  183. package/dist/test/Repo.test.js +0 -568
  184. package/dist/test/StorageSubsystem.test.d.ts +0 -2
  185. package/dist/test/StorageSubsystem.test.d.ts.map +0 -1
  186. package/dist/test/StorageSubsystem.test.js +0 -56
  187. package/dist/test/helpers/DummyNetworkAdapter.d.ts +0 -9
  188. package/dist/test/helpers/DummyNetworkAdapter.d.ts.map +0 -1
  189. package/dist/test/helpers/DummyNetworkAdapter.js +0 -15
  190. package/dist/test/helpers/DummyStorageAdapter.d.ts +0 -16
  191. package/dist/test/helpers/DummyStorageAdapter.d.ts.map +0 -1
  192. package/dist/test/helpers/DummyStorageAdapter.js +0 -33
  193. package/dist/test/helpers/generate-large-object.d.ts +0 -5
  194. package/dist/test/helpers/generate-large-object.d.ts.map +0 -1
  195. package/dist/test/helpers/generate-large-object.js +0 -9
  196. package/dist/test/helpers/getRandomItem.d.ts +0 -2
  197. package/dist/test/helpers/getRandomItem.d.ts.map +0 -1
  198. package/dist/test/helpers/getRandomItem.js +0 -4
  199. package/dist/test/types.d.ts +0 -4
  200. package/dist/test/types.d.ts.map +0 -1
  201. package/dist/test/types.js +0 -1
  202. package/src/CollectionHandle.ts +0 -54
  203. package/src/ferigan.ts +0 -184
@@ -1,13 +1,16 @@
1
- import * as A from "@automerge/automerge/next"
1
+ import { next as A } from "@automerge/automerge"
2
2
  import assert from "assert"
3
3
  import { decode } from "cbor-x"
4
- import { describe, it, vi } from "vitest"
5
- import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
4
+ import { describe, expect, it, vi } from "vitest"
5
+ import {
6
+ encodeHeads,
7
+ generateAutomergeUrl,
8
+ parseAutomergeUrl,
9
+ } from "../src/AutomergeUrl.js"
6
10
  import { eventPromise } from "../src/helpers/eventPromise.js"
7
11
  import { pause } from "../src/helpers/pause.js"
8
12
  import { DocHandle, DocHandleChangePayload } from "../src/index.js"
9
13
  import { TestDoc } from "./types.js"
10
- import { UNLOADED } from "../src/DocHandle.js"
11
14
 
12
15
  describe("DocHandle", () => {
13
16
  const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId
@@ -35,7 +38,7 @@ describe("DocHandle", () => {
35
38
  handle.update(doc => docFromMockStorage(doc))
36
39
 
37
40
  assert.equal(handle.isReady(), true)
38
- const doc = await handle.doc()
41
+ const doc = handle.doc()
39
42
  assert.equal(doc?.foo, "bar")
40
43
  })
41
44
 
@@ -47,13 +50,13 @@ describe("DocHandle", () => {
47
50
  handle.update(doc => docFromMockStorage(doc))
48
51
 
49
52
  assert.equal(handle.isReady(), true)
50
- const doc = await handle.doc()
51
- assert.deepEqual(doc, handle.docSync())
53
+ const doc = handle.doc()
54
+ assert.deepEqual(doc, handle.doc())
52
55
  })
53
56
 
54
- it("should return undefined if we access the doc before ready", async () => {
57
+ it("should throw an exception if we access the doc before ready", async () => {
55
58
  const handle = new DocHandle<TestDoc>(TEST_ID)
56
- assert.equal(handle.docSync(), undefined)
59
+ assert.throws(() => handle.doc())
57
60
  })
58
61
 
59
62
  it("should not return a doc until ready", async () => {
@@ -63,7 +66,7 @@ describe("DocHandle", () => {
63
66
  // simulate loading from storage
64
67
  handle.update(doc => docFromMockStorage(doc))
65
68
 
66
- const doc = await handle.doc()
69
+ const doc = handle.doc()
67
70
 
68
71
  assert.equal(handle.isReady(), true)
69
72
  assert.equal(doc?.foo, "bar")
@@ -83,15 +86,15 @@ describe("DocHandle", () => {
83
86
  handle.change(d => (d.foo = "bar"))
84
87
  assert.equal(handle.isReady(), true)
85
88
 
86
- const heads = A.getHeads(handle.docSync())
89
+ const heads = encodeHeads(A.getHeads(handle.doc()))
87
90
  assert.notDeepEqual(handle.heads(), [])
88
91
  assert.deepEqual(heads, handle.heads())
89
92
  })
90
93
 
91
- it("should return undefined if the heads aren't loaded", async () => {
94
+ it("should throw an if the heads aren't loaded", async () => {
92
95
  const handle = new DocHandle<TestDoc>(TEST_ID)
93
96
  assert.equal(handle.isReady(), false)
94
- assert.deepEqual(handle.heads(), undefined)
97
+ expect(() => handle.heads()).toThrow("DocHandle is not ready")
95
98
  })
96
99
 
97
100
  it("should return the history when requested", async () => {
@@ -113,8 +116,45 @@ describe("DocHandle", () => {
113
116
  assert.equal(handle.isReady(), true)
114
117
 
115
118
  const history = handle.history()
116
- const view = handle.view(history[1])
117
- assert.deepEqual(view, { foo: "one" })
119
+ const viewHandle = handle.view(history[1])
120
+ assert.deepEqual(await viewHandle.doc(), { foo: "one" })
121
+ })
122
+
123
+ it("should support fixed heads from construction", async () => {
124
+ const handle = setup()
125
+ handle.change(d => (d.foo = "zero"))
126
+ handle.change(d => (d.foo = "one"))
127
+
128
+ const history = handle.history()
129
+ const viewHandle = new DocHandle<TestDoc>(TEST_ID, { heads: history[0] })
130
+ viewHandle.update(() => A.clone(handle.doc()!))
131
+ viewHandle.doneLoading()
132
+
133
+ assert.deepEqual(await viewHandle.doc(), { foo: "zero" })
134
+ })
135
+
136
+ it("should prevent changes on fixed-heads handles", async () => {
137
+ const handle = setup()
138
+ handle.change(d => (d.foo = "zero"))
139
+ const viewHandle = handle.view(handle.heads()!)
140
+
141
+ assert.throws(() => viewHandle.change(d => (d.foo = "one")))
142
+ assert.throws(() =>
143
+ viewHandle.changeAt(handle.heads()!, d => (d.foo = "one"))
144
+ )
145
+ assert.throws(() => viewHandle.merge(handle))
146
+ })
147
+
148
+ it("should return fixed heads from heads()", async () => {
149
+ const handle = setup()
150
+ handle.change(d => (d.foo = "zero"))
151
+ const originalHeads = handle.heads()!
152
+
153
+ handle.change(d => (d.foo = "one"))
154
+ const viewHandle = handle.view(originalHeads)
155
+
156
+ assert.deepEqual(viewHandle.heads(), originalHeads)
157
+ assert.notDeepEqual(viewHandle.heads(), handle.heads())
118
158
  })
119
159
 
120
160
  it("should return diffs", async () => {
@@ -154,6 +194,31 @@ describe("DocHandle", () => {
154
194
  ])
155
195
  })
156
196
 
197
+ it("should support diffing against another handle", async () => {
198
+ const handle = setup()
199
+ handle.change(d => (d.foo = "zero"))
200
+ const viewHandle = handle.view(handle.heads()!)
201
+
202
+ handle.change(d => (d.foo = "one"))
203
+
204
+ const patches = viewHandle.diff(handle)
205
+ assert.deepEqual(patches, [
206
+ { action: "put", path: ["foo"], value: "" },
207
+ { action: "splice", path: ["foo", 0], value: "one" },
208
+ ])
209
+ })
210
+
211
+ // TODO: alexg -- should i remove this test? should this fail or no?
212
+ it.skip("should fail diffing against unrelated handles", async () => {
213
+ const handle1 = setup()
214
+ const handle2 = setup()
215
+
216
+ handle1.change(d => (d.foo = "zero"))
217
+ handle2.change(d => (d.foo = "one"))
218
+
219
+ assert.throws(() => handle1.diff(handle2))
220
+ })
221
+
157
222
  it("should allow direct access to decoded changes", async () => {
158
223
  const handle = setup()
159
224
  const time = Date.now()
@@ -194,8 +259,6 @@ describe("DocHandle", () => {
194
259
  const handle = new DocHandle<TestDoc>(TEST_ID)
195
260
  assert.equal(handle.isReady(), false)
196
261
 
197
- handle.doc()
198
-
199
262
  assert(vi.getTimerCount() > timerCount)
200
263
 
201
264
  // simulate loading from storage
@@ -220,7 +283,7 @@ describe("DocHandle", () => {
220
283
  assert.equal(handle.isReady(), true)
221
284
  handle.change(d => (d.foo = "pizza"))
222
285
 
223
- const doc = await handle.doc()
286
+ const doc = handle.doc()
224
287
  assert.equal(doc?.foo, "pizza")
225
288
  })
226
289
 
@@ -230,7 +293,9 @@ describe("DocHandle", () => {
230
293
  // we don't have it in storage, so we request it from the network
231
294
  handle.request()
232
295
 
233
- assert.equal(handle.docSync(), undefined)
296
+ await expect(() => {
297
+ handle.doc()
298
+ }).toThrowError("DocHandle is not ready")
234
299
  assert.equal(handle.isReady(), false)
235
300
  assert.throws(() => handle.change(_ => {}))
236
301
  })
@@ -246,7 +311,7 @@ describe("DocHandle", () => {
246
311
  return A.change(doc, d => (d.foo = "bar"))
247
312
  })
248
313
 
249
- const doc = await handle.doc()
314
+ const doc = handle.doc()
250
315
  assert.equal(handle.isReady(), true)
251
316
  assert.equal(doc?.foo, "bar")
252
317
  })
@@ -262,7 +327,7 @@ describe("DocHandle", () => {
262
327
  doc.foo = "bar"
263
328
  })
264
329
 
265
- const doc = await handle.doc()
330
+ const doc = handle.doc()
266
331
  assert.equal(doc?.foo, "bar")
267
332
 
268
333
  const changePayload = await p
@@ -287,7 +352,7 @@ describe("DocHandle", () => {
287
352
 
288
353
  const p = new Promise<void>(resolve =>
289
354
  handle.once("change", ({ handle, doc }) => {
290
- assert.equal(handle.docSync()?.foo, doc.foo)
355
+ assert.equal(handle.doc()?.foo, doc.foo)
291
356
 
292
357
  resolve()
293
358
  })
@@ -324,7 +389,7 @@ describe("DocHandle", () => {
324
389
  doc.foo = "baz"
325
390
  })
326
391
 
327
- const doc = await handle.doc()
392
+ const doc = handle.doc()
328
393
  assert.equal(doc?.foo, "baz")
329
394
 
330
395
  return p
@@ -339,7 +404,7 @@ describe("DocHandle", () => {
339
404
  })
340
405
 
341
406
  await p
342
- const doc = await handle.doc()
407
+ const doc = handle.doc()
343
408
  assert.equal(doc?.foo, "bar")
344
409
  })
345
410
 
@@ -359,11 +424,7 @@ describe("DocHandle", () => {
359
424
  // set docHandle time out after 5 ms
360
425
  const handle = new DocHandle<TestDoc>(TEST_ID, { timeoutDelay: 5 })
361
426
 
362
- const doc = await handle.doc()
363
-
364
- assert.equal(doc, undefined)
365
-
366
- assert.equal(handle.state, "unavailable")
427
+ expect(() => handle.doc()).toThrowError("DocHandle is not ready")
367
428
  })
368
429
 
369
430
  it("should not time out if the document is loaded in time", async () => {
@@ -374,11 +435,11 @@ describe("DocHandle", () => {
374
435
  handle.update(doc => docFromMockStorage(doc))
375
436
 
376
437
  // now it should not time out
377
- const doc = await handle.doc()
438
+ const doc = handle.doc()
378
439
  assert.equal(doc?.foo, "bar")
379
440
  })
380
441
 
381
- it("should be undefined if loading from the network times out", async () => {
442
+ it("should throw an exception if loading from the network times out", async () => {
382
443
  // set docHandle time out after 5 ms
383
444
  const handle = new DocHandle<TestDoc>(TEST_ID, { timeoutDelay: 5 })
384
445
 
@@ -388,8 +449,7 @@ describe("DocHandle", () => {
388
449
  // there's no update
389
450
  await pause(10)
390
451
 
391
- const doc = await handle.doc()
392
- assert.equal(doc, undefined)
452
+ expect(() => handle.doc()).toThrowError("DocHandle is not ready")
393
453
  })
394
454
 
395
455
  it("should not time out if the document is updated in time", async () => {
@@ -407,7 +467,7 @@ describe("DocHandle", () => {
407
467
  // now it should not time out
408
468
  await pause(5)
409
469
 
410
- const doc = await handle.doc()
470
+ const doc = handle.doc()
411
471
  assert.equal(doc?.foo, "bar")
412
472
  })
413
473
 
@@ -423,49 +483,6 @@ describe("DocHandle", () => {
423
483
  assert.equal(handle.isDeleted(), true)
424
484
  })
425
485
 
426
- it("should clear document reference when unloaded", async () => {
427
- const handle = setup()
428
-
429
- handle.change(doc => {
430
- doc.foo = "bar"
431
- })
432
- const doc = await handle.doc()
433
- assert.equal(doc?.foo, "bar")
434
-
435
- handle.unload()
436
- assert.equal(handle.isUnloaded(), true)
437
-
438
- const clearedDoc = await handle.doc([UNLOADED])
439
- assert.notEqual(clearedDoc?.foo, "bar")
440
- })
441
-
442
- it("should allow reloading after unloading", async () => {
443
- const handle = setup()
444
-
445
- handle.change(doc => {
446
- doc.foo = "bar"
447
- })
448
- const doc = await handle.doc()
449
- assert.equal(doc?.foo, "bar")
450
-
451
- handle.unload()
452
-
453
- // reload to transition from unloaded to loading
454
- handle.reload()
455
-
456
- // simulate requesting from the network
457
- handle.request()
458
-
459
- // simulate updating from the network
460
- handle.update(doc => {
461
- return A.change(doc, d => (d.foo = "bar"))
462
- })
463
-
464
- const reloadedDoc = await handle.doc()
465
- assert.equal(handle.isReady(), true)
466
- assert.equal(reloadedDoc?.foo, "bar")
467
- })
468
-
469
486
  it("should allow changing at old heads", async () => {
470
487
  const handle = setup()
471
488
 
@@ -503,4 +520,71 @@ describe("DocHandle", () => {
503
520
  assert.deepStrictEqual(decode(data), message)
504
521
  })
505
522
  })
523
+
524
+ it("should cache view handles based on heads", async () => {
525
+ // Create and setup a document with some data
526
+ const handle = setup()
527
+ handle.change(doc => {
528
+ doc.foo = "Hello"
529
+ })
530
+ const heads1 = handle.heads()
531
+
532
+ // Make another change to get a different set of heads
533
+ handle.change(doc => {
534
+ doc.foo = "Hello, World!"
535
+ })
536
+
537
+ // Create a view at the first set of heads
538
+ const view1 = handle.view(heads1)
539
+
540
+ // Request the same view again
541
+ const view2 = handle.view(heads1)
542
+
543
+ // Verify we got the same handle instance back (cached version)
544
+ expect(view1).toBe(view2)
545
+
546
+ // Verify the contents are correct
547
+ expect(view1.doc().foo).toBe("Hello")
548
+
549
+ // Test with a different set of heads
550
+ const view3 = handle.view(handle.heads())
551
+ expect(view3).not.toBe(view1)
552
+ expect(view3.doc().foo).toBe("Hello, World!")
553
+ })
554
+
555
+ it("should improve performance when requesting the same view multiple times", () => {
556
+ // Create and setup a document with some data
557
+ const handle = setup()
558
+ handle.change(doc => {
559
+ doc.foo = "Hello"
560
+ })
561
+ const heads = handle.heads()
562
+
563
+ // First, measure time without cache (first access)
564
+ const startTimeNoCached = performance.now()
565
+ const firstView = handle.view(heads)
566
+ const endTimeNoCached = performance.now()
567
+
568
+ // Now measure with cache (subsequent accesses)
569
+ const startTimeCached = performance.now()
570
+ for (let i = 0; i < 100; i++) {
571
+ handle.view(heads)
572
+ }
573
+ const endTimeCached = performance.now()
574
+
575
+ // Assert that all views are the same instance
576
+ for (let i = 0; i < 10; i++) {
577
+ expect(handle.view(heads)).toBe(firstView)
578
+ }
579
+
580
+ // Calculate average times
581
+ const timeForFirstAccess = endTimeNoCached - startTimeNoCached
582
+ const timeForCachedAccesses = (endTimeCached - startTimeCached) / 100
583
+
584
+ console.log(`Time for first view (no cache): ${timeForFirstAccess}ms`)
585
+ console.log(`Average time per cached view: ${timeForCachedAccesses}ms`)
586
+
587
+ // Cached access should be significantly faster
588
+ expect(timeForCachedAccesses).toBeLessThan(timeForFirstAccess / 10)
589
+ })
506
590
  })
@@ -1,7 +1,11 @@
1
1
  import assert from "assert"
2
2
  import { describe, it } from "vitest"
3
3
  import { next as Automerge } from "@automerge/automerge"
4
- import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
4
+ import {
5
+ encodeHeads,
6
+ generateAutomergeUrl,
7
+ parseAutomergeUrl,
8
+ } from "../src/AutomergeUrl.js"
5
9
  import { DocHandle } from "../src/DocHandle.js"
6
10
  import { eventPromise } from "../src/helpers/eventPromise.js"
7
11
  import {
@@ -16,10 +20,9 @@ const alice = "alice" as PeerId
16
20
  const bob = "bob" as PeerId
17
21
  const charlie = "charlie" as PeerId
18
22
 
19
- describe.skip("DocSynchronizer", () => {
23
+ describe("DocSynchronizer", () => {
20
24
  let handle: DocHandle<TestDoc>
21
25
  let docSynchronizer: DocSynchronizer
22
- let beelay: Automerge.beelay.Beelay
23
26
 
24
27
  const setup = () => {
25
28
  const docId = parseAutomergeUrl(generateAutomergeUrl()).documentId
@@ -27,7 +30,6 @@ describe.skip("DocSynchronizer", () => {
27
30
  handle.doneLoading()
28
31
 
29
32
  docSynchronizer = new DocSynchronizer({
30
- beelay,
31
33
  handle: handle as DocHandle<unknown>,
32
34
  })
33
35
 
@@ -69,11 +71,14 @@ describe.skip("DocSynchronizer", () => {
69
71
 
70
72
  assert.equal(message1.peerId, "alice")
71
73
  assert.equal(message1.documentId, handle.documentId)
72
- assert.deepEqual(message1.syncState.lastSentHeads, [])
74
+ assert.deepStrictEqual(message1.syncState.lastSentHeads, [])
73
75
 
74
76
  assert.equal(message2.peerId, "alice")
75
77
  assert.equal(message2.documentId, handle.documentId)
76
- assert.deepEqual(message2.syncState.lastSentHeads, handle.heads())
78
+ assert.deepStrictEqual(
79
+ encodeHeads(message2.syncState.lastSentHeads),
80
+ handle.heads()
81
+ )
77
82
  })
78
83
 
79
84
  it("still syncs with a peer after it disconnects and reconnects", async () => {
@@ -108,7 +113,6 @@ describe.skip("DocSynchronizer", () => {
108
113
 
109
114
  const handle = new DocHandle<TestDoc>(docId, { isNew: false })
110
115
  docSynchronizer = new DocSynchronizer({
111
- beelay,
112
116
  handle: handle as DocHandle<unknown>,
113
117
  })
114
118
  docSynchronizer.beginSync([alice])
@@ -123,7 +127,6 @@ describe.skip("DocSynchronizer", () => {
123
127
 
124
128
  const bobHandle = new DocHandle<TestDoc>(docId, { isNew: false })
125
129
  const bobDocSynchronizer = new DocSynchronizer({
126
- beelay,
127
130
  handle: bobHandle as DocHandle<unknown>,
128
131
  })
129
132
  bobDocSynchronizer.beginSync([alice])
@@ -133,7 +136,6 @@ describe.skip("DocSynchronizer", () => {
133
136
  const aliceHandle = new DocHandle<TestDoc>(docId, { isNew: false })
134
137
  const aliceDocSynchronizer = new DocSynchronizer({
135
138
  handle: aliceHandle as DocHandle<unknown>,
136
- beelay,
137
139
  })
138
140
  aliceHandle.request()
139
141
 
@@ -10,7 +10,7 @@ import {
10
10
  } from "../src/network/messages.js"
11
11
  import { collectMessages } from "./helpers/collectMessages.js"
12
12
 
13
- describe.skip("RepoHeadsSubscriptions", () => {
13
+ describe("RepoHeadsSubscriptions", () => {
14
14
  const storageA = "remote-a" as StorageId
15
15
  const storageB = "remote-b" as StorageId
16
16
  const storageC = "remote-c" as StorageId