@automerge/automerge-repo 2.0.0-alpha.20 → 2.0.0-alpha.23
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 +8 -8
- package/dist/DocHandle.d.ts +10 -22
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +21 -51
- package/dist/FindProgress.d.ts +30 -0
- package/dist/FindProgress.d.ts.map +1 -0
- package/dist/FindProgress.js +1 -0
- package/dist/Repo.d.ts +9 -4
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +166 -69
- package/dist/helpers/abortable.d.ts +39 -0
- package/dist/helpers/abortable.d.ts.map +1 -0
- package/dist/helpers/abortable.js +45 -0
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +13 -13
- package/dist/synchronizer/CollectionSynchronizer.d.ts +2 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +18 -14
- package/dist/synchronizer/DocSynchronizer.d.ts +3 -2
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +43 -27
- package/fuzz/fuzz.ts +3 -3
- package/package.json +3 -4
- package/src/DocHandle.ts +23 -51
- package/src/FindProgress.ts +48 -0
- package/src/Repo.ts +187 -67
- package/src/helpers/abortable.ts +61 -0
- package/src/helpers/tests/network-adapter-tests.ts +14 -13
- package/src/synchronizer/CollectionSynchronizer.ts +18 -14
- package/src/synchronizer/DocSynchronizer.ts +51 -32
- package/test/CollectionSynchronizer.test.ts +4 -4
- package/test/DocHandle.test.ts +25 -74
- package/test/Repo.test.ts +169 -216
- package/test/remoteHeads.test.ts +27 -12
package/test/Repo.test.ts
CHANGED
|
@@ -8,9 +8,6 @@ import {
|
|
|
8
8
|
getHeadsFromUrl,
|
|
9
9
|
isValidAutomergeUrl,
|
|
10
10
|
parseAutomergeUrl,
|
|
11
|
-
UrlHeads,
|
|
12
|
-
} from "../src/AutomergeUrl.js"
|
|
13
|
-
import {
|
|
14
11
|
generateAutomergeUrl,
|
|
15
12
|
stringifyAutomergeUrl,
|
|
16
13
|
} from "../src/AutomergeUrl.js"
|
|
@@ -19,6 +16,7 @@ import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
|
19
16
|
import { pause } from "../src/helpers/pause.js"
|
|
20
17
|
import {
|
|
21
18
|
AnyDocumentId,
|
|
19
|
+
UrlHeads,
|
|
22
20
|
AutomergeUrl,
|
|
23
21
|
DocHandle,
|
|
24
22
|
DocumentId,
|
|
@@ -78,35 +76,34 @@ describe("Repo", () => {
|
|
|
78
76
|
it("can create a document with an initial value", async () => {
|
|
79
77
|
const { repo } = setup()
|
|
80
78
|
const handle = repo.create({ foo: "bar" })
|
|
81
|
-
|
|
82
|
-
assert.equal(handle.docSync().foo, "bar")
|
|
79
|
+
assert.equal(handle.doc().foo, "bar")
|
|
83
80
|
})
|
|
84
81
|
|
|
85
|
-
it("can find a document by url", () => {
|
|
82
|
+
it("can find a document by url", async () => {
|
|
86
83
|
const { repo } = setup()
|
|
87
84
|
const handle = repo.create<TestDoc>()
|
|
88
85
|
handle.change((d: TestDoc) => {
|
|
89
86
|
d.foo = "bar"
|
|
90
87
|
})
|
|
91
88
|
|
|
92
|
-
const handle2 = repo.find(handle.url)
|
|
89
|
+
const handle2 = await repo.find(handle.url)
|
|
93
90
|
assert.equal(handle, handle2)
|
|
94
|
-
assert.deepEqual(handle2.
|
|
91
|
+
assert.deepEqual(handle2.doc(), { foo: "bar" })
|
|
95
92
|
})
|
|
96
93
|
|
|
97
|
-
it("can find a document by its unprefixed document ID", () => {
|
|
94
|
+
it("can find a document by its unprefixed document ID", async () => {
|
|
98
95
|
const { repo } = setup()
|
|
99
96
|
const handle = repo.create<TestDoc>()
|
|
100
97
|
handle.change((d: TestDoc) => {
|
|
101
98
|
d.foo = "bar"
|
|
102
99
|
})
|
|
103
100
|
|
|
104
|
-
const handle2 = repo.find(handle.documentId)
|
|
101
|
+
const handle2 = await repo.find(handle.documentId)
|
|
105
102
|
assert.equal(handle, handle2)
|
|
106
|
-
assert.deepEqual(handle2.
|
|
103
|
+
assert.deepEqual(handle2.doc(), { foo: "bar" })
|
|
107
104
|
})
|
|
108
105
|
|
|
109
|
-
it("can find a document by legacy UUID (for now)", () => {
|
|
106
|
+
it("can find a document by legacy UUID (for now)", async () => {
|
|
110
107
|
disableConsoleWarn()
|
|
111
108
|
|
|
112
109
|
const { repo } = setup()
|
|
@@ -119,9 +116,9 @@ describe("Repo", () => {
|
|
|
119
116
|
const { binaryDocumentId } = parseAutomergeUrl(url)
|
|
120
117
|
const legacyDocId = Uuid.stringify(binaryDocumentId) as LegacyDocumentId
|
|
121
118
|
|
|
122
|
-
const handle2 = repo.find(legacyDocId)
|
|
119
|
+
const handle2 = await repo.find(legacyDocId)
|
|
123
120
|
assert.equal(handle, handle2)
|
|
124
|
-
assert.deepEqual(handle2.
|
|
121
|
+
assert.deepEqual(handle2.doc(), { foo: "bar" })
|
|
125
122
|
|
|
126
123
|
reenableConsoleWarn()
|
|
127
124
|
})
|
|
@@ -132,7 +129,7 @@ describe("Repo", () => {
|
|
|
132
129
|
handle.change(d => {
|
|
133
130
|
d.foo = "bar"
|
|
134
131
|
})
|
|
135
|
-
const v =
|
|
132
|
+
const v = handle.doc()
|
|
136
133
|
assert.equal(handle.isReady(), true)
|
|
137
134
|
assert.equal(v.foo, "bar")
|
|
138
135
|
})
|
|
@@ -146,8 +143,8 @@ describe("Repo", () => {
|
|
|
146
143
|
const handle2 = repo.clone(handle)
|
|
147
144
|
assert.equal(handle2.isReady(), true)
|
|
148
145
|
assert.notEqual(handle.documentId, handle2.documentId)
|
|
149
|
-
assert.deepStrictEqual(handle.
|
|
150
|
-
assert.deepStrictEqual(handle2.
|
|
146
|
+
assert.deepStrictEqual(handle.doc(), handle2.doc())
|
|
147
|
+
assert.deepStrictEqual(handle2.doc(), { foo: "bar" })
|
|
151
148
|
})
|
|
152
149
|
|
|
153
150
|
it("the cloned documents are distinct", () => {
|
|
@@ -165,9 +162,9 @@ describe("Repo", () => {
|
|
|
165
162
|
d.baz = "baz"
|
|
166
163
|
})
|
|
167
164
|
|
|
168
|
-
assert.notDeepStrictEqual(handle.
|
|
169
|
-
assert.deepStrictEqual(handle.
|
|
170
|
-
assert.deepStrictEqual(handle2.
|
|
165
|
+
assert.notDeepStrictEqual(handle.doc(), handle2.doc())
|
|
166
|
+
assert.deepStrictEqual(handle.doc(), { foo: "bar", bar: "bif" })
|
|
167
|
+
assert.deepStrictEqual(handle2.doc(), { foo: "bar", baz: "baz" })
|
|
171
168
|
})
|
|
172
169
|
|
|
173
170
|
it("the cloned documents can merge", () => {
|
|
@@ -187,59 +184,47 @@ describe("Repo", () => {
|
|
|
187
184
|
|
|
188
185
|
handle.merge(handle2)
|
|
189
186
|
|
|
190
|
-
assert.deepStrictEqual(handle.
|
|
187
|
+
assert.deepStrictEqual(handle.doc(), {
|
|
191
188
|
foo: "bar",
|
|
192
189
|
bar: "bif",
|
|
193
190
|
baz: "baz",
|
|
194
191
|
})
|
|
195
192
|
// only the one handle should be changed
|
|
196
|
-
assert.deepStrictEqual(handle2.
|
|
193
|
+
assert.deepStrictEqual(handle2.doc(), { foo: "bar", baz: "baz" })
|
|
197
194
|
})
|
|
198
195
|
|
|
199
196
|
it("throws an error if we try to find a handle with an invalid AutomergeUrl", async () => {
|
|
200
197
|
const { repo } = setup()
|
|
201
|
-
|
|
202
|
-
repo.find<TestDoc>("invalid-url" as unknown as AutomergeUrl)
|
|
203
|
-
}
|
|
204
|
-
assert.equal(e.message, "Invalid AutomergeUrl: 'invalid-url'")
|
|
205
|
-
}
|
|
198
|
+
await expect(async () => {
|
|
199
|
+
await repo.find<TestDoc>("invalid-url" as unknown as AutomergeUrl)
|
|
200
|
+
}).rejects.toThrow("Invalid AutomergeUrl: 'invalid-url'")
|
|
206
201
|
})
|
|
207
202
|
|
|
208
203
|
it("doesn't find a document that doesn't exist", async () => {
|
|
209
204
|
const { repo } = setup()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
assert.equal(handle.isReady(), false)
|
|
215
|
-
assert.equal(handle.state, "unavailable")
|
|
216
|
-
const doc = await handle.doc()
|
|
217
|
-
assert.equal(doc, undefined)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it("emits an unavailable event when you don't have the document locally and are not connected to anyone", async () => {
|
|
221
|
-
const { repo } = setup()
|
|
222
|
-
const url = generateAutomergeUrl()
|
|
223
|
-
const handle = repo.find<TestDoc>(url)
|
|
224
|
-
assert.equal(handle.isReady(), false)
|
|
225
|
-
await eventPromise(handle, "unavailable")
|
|
205
|
+
await expect(async () => {
|
|
206
|
+
await repo.find<TestDoc>(generateAutomergeUrl())
|
|
207
|
+
}).rejects.toThrow(/Document (.*) is unavailable/)
|
|
226
208
|
})
|
|
227
209
|
|
|
228
210
|
it("doesn't mark a document as unavailable until network adapters are ready", async () => {
|
|
229
211
|
const { repo, networkAdapter } = setup({ startReady: false })
|
|
230
212
|
const url = generateAutomergeUrl()
|
|
231
|
-
const handle = repo.find<TestDoc>(url)
|
|
232
213
|
|
|
233
|
-
|
|
234
|
-
handle.on("unavailable", () => {
|
|
235
|
-
wasUnavailable = true
|
|
236
|
-
})
|
|
214
|
+
const attemptedFind = repo.find<TestDoc>(url)
|
|
237
215
|
|
|
238
|
-
|
|
239
|
-
|
|
216
|
+
// First verify it stays pending for 50ms
|
|
217
|
+
await expect(
|
|
218
|
+
Promise.race([attemptedFind, pause(50)])
|
|
219
|
+
).resolves.toBeUndefined()
|
|
240
220
|
|
|
221
|
+
// Trigger the rejection
|
|
241
222
|
networkAdapter.forceReady()
|
|
242
|
-
|
|
223
|
+
|
|
224
|
+
// Now verify it rejects
|
|
225
|
+
await expect(attemptedFind).rejects.toThrow(
|
|
226
|
+
/Document (.*) is unavailable/
|
|
227
|
+
)
|
|
243
228
|
})
|
|
244
229
|
|
|
245
230
|
it("can find a created document", async () => {
|
|
@@ -250,18 +235,18 @@ describe("Repo", () => {
|
|
|
250
235
|
})
|
|
251
236
|
assert.equal(handle.isReady(), true)
|
|
252
237
|
|
|
253
|
-
const bobHandle = repo.find<TestDoc>(handle.url)
|
|
238
|
+
const bobHandle = await repo.find<TestDoc>(handle.url)
|
|
254
239
|
|
|
255
240
|
assert.equal(handle, bobHandle)
|
|
256
241
|
assert.equal(handle.isReady(), true)
|
|
257
242
|
|
|
258
|
-
const v =
|
|
243
|
+
const v = bobHandle.doc()
|
|
259
244
|
assert.equal(v?.foo, "bar")
|
|
260
245
|
})
|
|
261
246
|
|
|
262
247
|
it("saves the document when creating it", async () => {
|
|
263
248
|
const { repo, storageAdapter } = setup()
|
|
264
|
-
const handle = repo.create<TestDoc>()
|
|
249
|
+
const handle = repo.create<TestDoc>({ foo: "saved" })
|
|
265
250
|
|
|
266
251
|
const repo2 = new Repo({
|
|
267
252
|
storage: storageAdapter,
|
|
@@ -269,9 +254,8 @@ describe("Repo", () => {
|
|
|
269
254
|
|
|
270
255
|
await repo.flush()
|
|
271
256
|
|
|
272
|
-
const bobHandle = repo2.find<TestDoc>(handle.url)
|
|
273
|
-
|
|
274
|
-
assert.equal(bobHandle.isReady(), true)
|
|
257
|
+
const bobHandle = await repo2.find<TestDoc>(handle.url)
|
|
258
|
+
assert.deepEqual(bobHandle.doc(), { foo: "saved" })
|
|
275
259
|
})
|
|
276
260
|
|
|
277
261
|
it("saves the document when changed and can find it again", async () => {
|
|
@@ -290,9 +274,9 @@ describe("Repo", () => {
|
|
|
290
274
|
storage: storageAdapter,
|
|
291
275
|
})
|
|
292
276
|
|
|
293
|
-
const bobHandle = repo2.find<TestDoc>(handle.url)
|
|
277
|
+
const bobHandle = await repo2.find<TestDoc>(handle.url)
|
|
294
278
|
|
|
295
|
-
const v =
|
|
279
|
+
const v = bobHandle.doc()
|
|
296
280
|
assert.equal(v?.foo, "bar")
|
|
297
281
|
})
|
|
298
282
|
|
|
@@ -304,7 +288,7 @@ describe("Repo", () => {
|
|
|
304
288
|
})
|
|
305
289
|
// we now have a snapshot and an incremental change in storage
|
|
306
290
|
assert.equal(handle.isReady(), true)
|
|
307
|
-
const foo =
|
|
291
|
+
const foo = handle.doc()
|
|
308
292
|
assert.equal(foo?.foo, "bar")
|
|
309
293
|
|
|
310
294
|
await pause()
|
|
@@ -321,7 +305,6 @@ describe("Repo", () => {
|
|
|
321
305
|
d.foo = "bar"
|
|
322
306
|
})
|
|
323
307
|
assert.equal(handle.isReady(), true)
|
|
324
|
-
await handle.doc()
|
|
325
308
|
|
|
326
309
|
await pause()
|
|
327
310
|
repo.delete(handle.url)
|
|
@@ -358,7 +341,7 @@ describe("Repo", () => {
|
|
|
358
341
|
|
|
359
342
|
const exported = await repo.export(handle.documentId)
|
|
360
343
|
const loaded = A.load(exported)
|
|
361
|
-
const doc =
|
|
344
|
+
const doc = handle.doc()
|
|
362
345
|
assert.deepEqual(doc, loaded)
|
|
363
346
|
})
|
|
364
347
|
|
|
@@ -392,9 +375,7 @@ describe("Repo", () => {
|
|
|
392
375
|
const repo2 = new Repo({
|
|
393
376
|
storage,
|
|
394
377
|
})
|
|
395
|
-
const handle2 = repo2.find(handle.url)
|
|
396
|
-
await handle2.doc()
|
|
397
|
-
|
|
378
|
+
const handle2 = await repo2.find(handle.url)
|
|
398
379
|
assert.deepEqual(storage.keys(), initialKeys)
|
|
399
380
|
})
|
|
400
381
|
|
|
@@ -420,9 +401,7 @@ describe("Repo", () => {
|
|
|
420
401
|
const repo2 = new Repo({
|
|
421
402
|
storage,
|
|
422
403
|
})
|
|
423
|
-
const handle2 = repo2.find(handle.url)
|
|
424
|
-
await handle2.doc()
|
|
425
|
-
|
|
404
|
+
const handle2 = await repo2.find(handle.url)
|
|
426
405
|
assert(storage.keys().length !== 0)
|
|
427
406
|
}
|
|
428
407
|
})
|
|
@@ -462,7 +441,7 @@ describe("Repo", () => {
|
|
|
462
441
|
|
|
463
442
|
const handle = repo.import<TestDoc>(saved)
|
|
464
443
|
assert.equal(handle.isReady(), true)
|
|
465
|
-
const v =
|
|
444
|
+
const v = handle.doc()
|
|
466
445
|
assert.equal(v?.foo, "bar")
|
|
467
446
|
|
|
468
447
|
expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
|
|
@@ -481,7 +460,7 @@ describe("Repo", () => {
|
|
|
481
460
|
const { repo } = setup()
|
|
482
461
|
// @ts-ignore - passing something other than UInt8Array
|
|
483
462
|
const handle = repo.import<TestDoc>(A.from({ foo: 123 }))
|
|
484
|
-
const doc =
|
|
463
|
+
const doc = handle.doc()
|
|
485
464
|
expect(doc).toEqual({})
|
|
486
465
|
})
|
|
487
466
|
|
|
@@ -489,7 +468,7 @@ describe("Repo", () => {
|
|
|
489
468
|
const { repo } = setup()
|
|
490
469
|
// @ts-ignore - passing something other than UInt8Array
|
|
491
470
|
const handle = repo.import<TestDoc>({ foo: 123 })
|
|
492
|
-
const doc =
|
|
471
|
+
const doc = handle.doc()
|
|
493
472
|
expect(doc).toEqual({})
|
|
494
473
|
})
|
|
495
474
|
|
|
@@ -497,14 +476,12 @@ describe("Repo", () => {
|
|
|
497
476
|
it("contains doc handle", async () => {
|
|
498
477
|
const { repo } = setup()
|
|
499
478
|
const handle = repo.create({ foo: "bar" })
|
|
500
|
-
await handle.doc()
|
|
501
479
|
assert(repo.handles[handle.documentId])
|
|
502
480
|
})
|
|
503
481
|
|
|
504
482
|
it("delete removes doc handle", async () => {
|
|
505
483
|
const { repo } = setup()
|
|
506
484
|
const handle = repo.create({ foo: "bar" })
|
|
507
|
-
await handle.doc()
|
|
508
485
|
await repo.delete(handle.documentId)
|
|
509
486
|
assert(repo.handles[handle.documentId] === undefined)
|
|
510
487
|
})
|
|
@@ -512,7 +489,6 @@ describe("Repo", () => {
|
|
|
512
489
|
it("removeFromCache removes doc handle", async () => {
|
|
513
490
|
const { repo } = setup()
|
|
514
491
|
const handle = repo.create({ foo: "bar" })
|
|
515
|
-
await handle.doc()
|
|
516
492
|
await repo.removeFromCache(handle.documentId)
|
|
517
493
|
assert(repo.handles[handle.documentId] === undefined)
|
|
518
494
|
})
|
|
@@ -571,8 +547,8 @@ describe("Repo", () => {
|
|
|
571
547
|
|
|
572
548
|
it("should not be in a new repo yet because the storage is slow", async () => {
|
|
573
549
|
const { pausedStorage, repo, handle, handle2 } = setup()
|
|
574
|
-
expect((await handle.doc()
|
|
575
|
-
expect((await handle2.doc()
|
|
550
|
+
expect((await handle).doc().foo).toEqual("first")
|
|
551
|
+
expect((await handle2).doc().foo).toEqual("second")
|
|
576
552
|
|
|
577
553
|
// Reload repo
|
|
578
554
|
const repo2 = new Repo({
|
|
@@ -580,9 +556,10 @@ describe("Repo", () => {
|
|
|
580
556
|
})
|
|
581
557
|
|
|
582
558
|
// Could not find the document that is not yet saved because of slow storage.
|
|
583
|
-
|
|
559
|
+
await expect(async () => {
|
|
560
|
+
const reloadedHandle = await repo2.find<{ foo: string }>(handle.url)
|
|
561
|
+
}).rejects.toThrow(/Document (.*) is unavailable/)
|
|
584
562
|
expect(pausedStorage.keys()).to.deep.equal([])
|
|
585
|
-
expect(await reloadedHandle.doc()).toEqual(undefined)
|
|
586
563
|
})
|
|
587
564
|
|
|
588
565
|
it("should be visible to a new repo after flush()", async () => {
|
|
@@ -602,10 +579,10 @@ describe("Repo", () => {
|
|
|
602
579
|
})
|
|
603
580
|
|
|
604
581
|
expect(
|
|
605
|
-
(await repo.find<{ foo: string }>(handle.documentId).doc()
|
|
582
|
+
(await repo.find<{ foo: string }>(handle.documentId)).doc().foo
|
|
606
583
|
).toEqual("first")
|
|
607
584
|
expect(
|
|
608
|
-
(await repo.find<{ foo: string }>(handle2.documentId).doc()
|
|
585
|
+
(await repo.find<{ foo: string }>(handle2.documentId)).doc().foo
|
|
609
586
|
).toEqual("second")
|
|
610
587
|
}
|
|
611
588
|
})
|
|
@@ -627,13 +604,13 @@ describe("Repo", () => {
|
|
|
627
604
|
})
|
|
628
605
|
|
|
629
606
|
expect(
|
|
630
|
-
(await repo.find<{ foo: string }>(handle.documentId).doc()
|
|
607
|
+
(await repo.find<{ foo: string }>(handle.documentId)).doc().foo
|
|
631
608
|
).toEqual("first")
|
|
632
609
|
// Really, it's okay if the second one is also flushed but I'm forcing the issue
|
|
633
610
|
// in the test storage engine above to make sure the behaviour is as documented
|
|
634
|
-
expect(
|
|
635
|
-
await repo.find<{ foo: string }>(handle2.documentId).doc()
|
|
636
|
-
).
|
|
611
|
+
await expect(async () => {
|
|
612
|
+
;(await repo.find<{ foo: string }>(handle2.documentId)).doc()
|
|
613
|
+
}).rejects.toThrow(/Document (.*) is unavailable/)
|
|
637
614
|
}
|
|
638
615
|
})
|
|
639
616
|
|
|
@@ -681,7 +658,7 @@ describe("Repo", () => {
|
|
|
681
658
|
|
|
682
659
|
if (idx < numberOfPeers - 1) {
|
|
683
660
|
network.push(pair[0])
|
|
684
|
-
pair[0].whenReady()
|
|
661
|
+
networkReady.push(pair[0].whenReady())
|
|
685
662
|
}
|
|
686
663
|
|
|
687
664
|
const repo = new Repo({
|
|
@@ -712,7 +689,6 @@ describe("Repo", () => {
|
|
|
712
689
|
}
|
|
713
690
|
|
|
714
691
|
await connectedPromise
|
|
715
|
-
|
|
716
692
|
return { repos }
|
|
717
693
|
}
|
|
718
694
|
|
|
@@ -724,10 +700,14 @@ describe("Repo", () => {
|
|
|
724
700
|
d.foo = "bar"
|
|
725
701
|
})
|
|
726
702
|
|
|
727
|
-
const handleN = repos[numberOfPeers - 1].find<TestDoc>(handle0.url)
|
|
703
|
+
const handleN = await repos[numberOfPeers - 1].find<TestDoc>(handle0.url)
|
|
704
|
+
assert.deepStrictEqual(handleN.doc(), { foo: "bar" })
|
|
728
705
|
|
|
729
|
-
|
|
730
|
-
|
|
706
|
+
const handleNBack = repos[numberOfPeers - 1].create({
|
|
707
|
+
foo: "reverse-trip",
|
|
708
|
+
})
|
|
709
|
+
const handle0Back = await repos[0].find<TestDoc>(handleNBack.url)
|
|
710
|
+
assert.deepStrictEqual(handle0Back.doc(), { foo: "reverse-trip" })
|
|
731
711
|
})
|
|
732
712
|
|
|
733
713
|
const setup = async ({
|
|
@@ -854,9 +834,8 @@ describe("Repo", () => {
|
|
|
854
834
|
it("changes are replicated from aliceRepo to bobRepo", async () => {
|
|
855
835
|
const { bobRepo, aliceHandle, teardown } = await setup()
|
|
856
836
|
|
|
857
|
-
const bobHandle = bobRepo.find<TestDoc>(aliceHandle.url)
|
|
858
|
-
|
|
859
|
-
const bobDoc = await bobHandle.doc()
|
|
837
|
+
const bobHandle = await bobRepo.find<TestDoc>(aliceHandle.url)
|
|
838
|
+
const bobDoc = bobHandle.doc()
|
|
860
839
|
assert.deepStrictEqual(bobDoc, { foo: "bar" })
|
|
861
840
|
teardown()
|
|
862
841
|
})
|
|
@@ -864,9 +843,8 @@ describe("Repo", () => {
|
|
|
864
843
|
it("can load a document from aliceRepo on charlieRepo", async () => {
|
|
865
844
|
const { charlieRepo, aliceHandle, teardown } = await setup()
|
|
866
845
|
|
|
867
|
-
const handle3 = charlieRepo.find<TestDoc>(aliceHandle.url)
|
|
868
|
-
|
|
869
|
-
const doc3 = await handle3.doc()
|
|
846
|
+
const handle3 = await charlieRepo.find<TestDoc>(aliceHandle.url)
|
|
847
|
+
const doc3 = handle3.doc()
|
|
870
848
|
assert.deepStrictEqual(doc3, { foo: "bar" })
|
|
871
849
|
teardown()
|
|
872
850
|
})
|
|
@@ -885,12 +863,11 @@ describe("Repo", () => {
|
|
|
885
863
|
await bobRepo2.flush()
|
|
886
864
|
|
|
887
865
|
// Now, let's load it on the original bob repo (which shares a "disk")
|
|
888
|
-
const bobFoundIt = bobRepo.find<TestDoc>(inStorageHandle.url)
|
|
889
|
-
await bobFoundIt.whenReady()
|
|
866
|
+
const bobFoundIt = await bobRepo.find<TestDoc>(inStorageHandle.url)
|
|
890
867
|
|
|
891
868
|
// Before checking if it syncs, make sure we have it!
|
|
892
869
|
// (This behaviour is mostly test-validation, we are already testing load/save elsewhere.)
|
|
893
|
-
assert.deepStrictEqual(
|
|
870
|
+
assert.deepStrictEqual(bobFoundIt.doc(), { foo: "foundOnFakeDisk" })
|
|
894
871
|
|
|
895
872
|
await pause(10)
|
|
896
873
|
|
|
@@ -930,11 +907,8 @@ describe("Repo", () => {
|
|
|
930
907
|
it("charlieRepo can request a document not initially shared with it", async () => {
|
|
931
908
|
const { charlieRepo, notForCharlie, teardown } = await setup()
|
|
932
909
|
|
|
933
|
-
const handle = charlieRepo.find<TestDoc>(notForCharlie)
|
|
934
|
-
|
|
935
|
-
await pause(50)
|
|
936
|
-
|
|
937
|
-
const doc = await handle.doc()
|
|
910
|
+
const handle = await charlieRepo.find<TestDoc>(notForCharlie)
|
|
911
|
+
const doc = handle.doc()
|
|
938
912
|
|
|
939
913
|
assert.deepStrictEqual(doc, { foo: "baz" })
|
|
940
914
|
|
|
@@ -944,11 +918,11 @@ describe("Repo", () => {
|
|
|
944
918
|
it("charlieRepo can request a document across a network of multiple peers", async () => {
|
|
945
919
|
const { charlieRepo, notForBob, teardown } = await setup()
|
|
946
920
|
|
|
947
|
-
const handle = charlieRepo.find<TestDoc>(notForBob)
|
|
921
|
+
const handle = await charlieRepo.find<TestDoc>(notForBob)
|
|
948
922
|
|
|
949
923
|
await pause(50)
|
|
950
924
|
|
|
951
|
-
const doc =
|
|
925
|
+
const doc = handle.doc()
|
|
952
926
|
assert.deepStrictEqual(doc, { foo: "bap" })
|
|
953
927
|
|
|
954
928
|
teardown()
|
|
@@ -957,42 +931,10 @@ describe("Repo", () => {
|
|
|
957
931
|
it("doesn't find a document which doesn't exist anywhere on the network", async () => {
|
|
958
932
|
const { charlieRepo, teardown } = await setup()
|
|
959
933
|
const url = generateAutomergeUrl()
|
|
960
|
-
const handle = charlieRepo.find<TestDoc>(url)
|
|
961
|
-
assert.equal(handle.isReady(), false)
|
|
962
|
-
|
|
963
|
-
const doc = await handle.doc()
|
|
964
|
-
assert.equal(doc, undefined)
|
|
965
|
-
|
|
966
|
-
teardown()
|
|
967
|
-
})
|
|
968
|
-
|
|
969
|
-
it("emits an unavailable event when it's not found on the network", async () => {
|
|
970
|
-
const { aliceRepo, teardown } = await setup()
|
|
971
|
-
const url = generateAutomergeUrl()
|
|
972
|
-
const handle = aliceRepo.find(url)
|
|
973
|
-
assert.equal(handle.isReady(), false)
|
|
974
|
-
await eventPromise(handle, "unavailable")
|
|
975
|
-
teardown()
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
it("emits an unavailable event every time an unavailable doc is requested", async () => {
|
|
979
|
-
const { charlieRepo, teardown } = await setup()
|
|
980
|
-
const url = generateAutomergeUrl()
|
|
981
|
-
const handle = charlieRepo.find<TestDoc>(url)
|
|
982
|
-
assert.equal(handle.isReady(), false)
|
|
983
|
-
|
|
984
|
-
await Promise.all([
|
|
985
|
-
eventPromise(handle, "unavailable"),
|
|
986
|
-
eventPromise(charlieRepo, "unavailable-document"),
|
|
987
|
-
])
|
|
988
934
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
await Promise.all([
|
|
993
|
-
eventPromise(handle, "unavailable"),
|
|
994
|
-
eventPromise(charlieRepo, "unavailable-document"),
|
|
995
|
-
])
|
|
935
|
+
await expect(charlieRepo.find<TestDoc>(url)).rejects.toThrow(
|
|
936
|
+
/Document (.*) is unavailable/
|
|
937
|
+
)
|
|
996
938
|
|
|
997
939
|
teardown()
|
|
998
940
|
})
|
|
@@ -1007,21 +949,23 @@ describe("Repo", () => {
|
|
|
1007
949
|
} = await setup({ connectAlice: false })
|
|
1008
950
|
|
|
1009
951
|
const url = stringifyAutomergeUrl({ documentId: notForCharlie })
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
await eventPromise(handle, "unavailable")
|
|
952
|
+
await expect(charlieRepo.find<TestDoc>(url)).rejects.toThrow(
|
|
953
|
+
/Document (.*) is unavailable/
|
|
954
|
+
)
|
|
1014
955
|
|
|
1015
956
|
connectAliceToBob()
|
|
1016
957
|
|
|
1017
958
|
await eventPromise(aliceRepo.networkSubsystem, "peer")
|
|
1018
959
|
|
|
1019
|
-
|
|
960
|
+
// Not sure why we need this pause here, but... we do.
|
|
961
|
+
await pause(150)
|
|
962
|
+
const handle = await charlieRepo.find<TestDoc>(url)
|
|
963
|
+
const doc = handle.doc()
|
|
1020
964
|
assert.deepStrictEqual(doc, { foo: "baz" })
|
|
1021
965
|
|
|
1022
966
|
// an additional find should also return the correct resolved document
|
|
1023
|
-
const handle2 = charlieRepo.find<TestDoc>(url)
|
|
1024
|
-
const doc2 =
|
|
967
|
+
const handle2 = await charlieRepo.find<TestDoc>(url)
|
|
968
|
+
const doc2 = handle2.doc()
|
|
1025
969
|
assert.deepStrictEqual(doc2, { foo: "baz" })
|
|
1026
970
|
|
|
1027
971
|
teardown()
|
|
@@ -1057,11 +1001,9 @@ describe("Repo", () => {
|
|
|
1057
1001
|
sharePolicy: async () => true,
|
|
1058
1002
|
})
|
|
1059
1003
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
// the repo has no storage.
|
|
1064
|
-
await eventPromise(handle, "unavailable")
|
|
1004
|
+
await expect(a.find<TestDoc>(url)).rejects.toThrow(
|
|
1005
|
+
/Document (.*) is unavailable/
|
|
1006
|
+
)
|
|
1065
1007
|
|
|
1066
1008
|
// Now create a repo pointing at the storage containing the document and
|
|
1067
1009
|
// connect it to the other end of the MessageChannel
|
|
@@ -1071,9 +1013,14 @@ describe("Repo", () => {
|
|
|
1071
1013
|
network: [new MessageChannelNetworkAdapter(ba)],
|
|
1072
1014
|
})
|
|
1073
1015
|
|
|
1016
|
+
// We need a proper peer status API so we can tell when the
|
|
1017
|
+
// peer is connected. For now we just wait a bit.
|
|
1018
|
+
await pause(50)
|
|
1019
|
+
|
|
1074
1020
|
// The empty repo should be notified of the new peer, send it a request
|
|
1075
1021
|
// and eventually resolve the handle to "READY"
|
|
1076
|
-
await
|
|
1022
|
+
const handle = await a.find<TestDoc>(url)
|
|
1023
|
+
expect(handle.state).toBe("ready")
|
|
1077
1024
|
})
|
|
1078
1025
|
|
|
1079
1026
|
it("a deleted document from charlieRepo can be refetched", async () => {
|
|
@@ -1089,9 +1036,8 @@ describe("Repo", () => {
|
|
|
1089
1036
|
})
|
|
1090
1037
|
await changePromise
|
|
1091
1038
|
|
|
1092
|
-
const handle3 = charlieRepo.find<TestDoc>(aliceHandle.url)
|
|
1093
|
-
|
|
1094
|
-
const doc3 = await handle3.doc()
|
|
1039
|
+
const handle3 = await charlieRepo.find<TestDoc>(aliceHandle.url)
|
|
1040
|
+
const doc3 = handle3.doc()
|
|
1095
1041
|
|
|
1096
1042
|
assert.deepStrictEqual(doc3, { foo: "baz" })
|
|
1097
1043
|
|
|
@@ -1115,11 +1061,6 @@ describe("Repo", () => {
|
|
|
1115
1061
|
: // tails, pick a random doc
|
|
1116
1062
|
(getRandomItem(docs) as DocHandle<TestDoc>)
|
|
1117
1063
|
|
|
1118
|
-
// make sure the doc is ready
|
|
1119
|
-
if (!doc.isReady()) {
|
|
1120
|
-
await doc.doc()
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
1064
|
// make a random change to it
|
|
1124
1065
|
doc.change(d => {
|
|
1125
1066
|
d.foo = Math.random().toString()
|
|
@@ -1135,10 +1076,10 @@ describe("Repo", () => {
|
|
|
1135
1076
|
|
|
1136
1077
|
const data = { presence: "alice" }
|
|
1137
1078
|
|
|
1138
|
-
const aliceHandle = aliceRepo.find<TestDoc>(
|
|
1079
|
+
const aliceHandle = await aliceRepo.find<TestDoc>(
|
|
1139
1080
|
stringifyAutomergeUrl({ documentId: notForCharlie })
|
|
1140
1081
|
)
|
|
1141
|
-
const bobHandle = bobRepo.find<TestDoc>(
|
|
1082
|
+
const bobHandle = await bobRepo.find<TestDoc>(
|
|
1142
1083
|
stringifyAutomergeUrl({ documentId: notForCharlie })
|
|
1143
1084
|
)
|
|
1144
1085
|
|
|
@@ -1291,8 +1232,7 @@ describe("Repo", () => {
|
|
|
1291
1232
|
})
|
|
1292
1233
|
})
|
|
1293
1234
|
|
|
1294
|
-
const charlieHandle = charlieRepo.find<TestDoc>(handle.url)
|
|
1295
|
-
await charlieHandle.whenReady()
|
|
1235
|
+
const charlieHandle = await charlieRepo.find<TestDoc>(handle.url)
|
|
1296
1236
|
|
|
1297
1237
|
// make a change on charlie
|
|
1298
1238
|
charlieHandle.change(d => {
|
|
@@ -1329,34 +1269,6 @@ describe("Repo", () => {
|
|
|
1329
1269
|
})
|
|
1330
1270
|
})
|
|
1331
1271
|
|
|
1332
|
-
it("peer receives a document when connection is recovered", async () => {
|
|
1333
|
-
const alice = "alice" as PeerId
|
|
1334
|
-
const bob = "bob" as PeerId
|
|
1335
|
-
const [aliceAdapter, bobAdapter] = DummyNetworkAdapter.createConnectedPair()
|
|
1336
|
-
const aliceRepo = new Repo({
|
|
1337
|
-
network: [aliceAdapter],
|
|
1338
|
-
peerId: alice,
|
|
1339
|
-
})
|
|
1340
|
-
const bobRepo = new Repo({
|
|
1341
|
-
network: [bobAdapter],
|
|
1342
|
-
peerId: bob,
|
|
1343
|
-
})
|
|
1344
|
-
const aliceDoc = aliceRepo.create()
|
|
1345
|
-
aliceDoc.change((doc: any) => (doc.text = "Hello world"))
|
|
1346
|
-
|
|
1347
|
-
const bobDoc = bobRepo.find(aliceDoc.url)
|
|
1348
|
-
await eventPromise(bobDoc, "unavailable")
|
|
1349
|
-
|
|
1350
|
-
aliceAdapter.peerCandidate(bob)
|
|
1351
|
-
// Bob isn't yet connected to Alice and can't respond to her sync message
|
|
1352
|
-
await pause(100)
|
|
1353
|
-
bobAdapter.peerCandidate(alice)
|
|
1354
|
-
|
|
1355
|
-
await bobDoc.whenReady()
|
|
1356
|
-
|
|
1357
|
-
assert.equal(bobDoc.isReady(), true)
|
|
1358
|
-
})
|
|
1359
|
-
|
|
1360
1272
|
describe("with peers (mesh network)", () => {
|
|
1361
1273
|
const setup = async () => {
|
|
1362
1274
|
// Set up three repos; connect Alice to Bob, Bob to Charlie, and Alice to Charlie
|
|
@@ -1418,8 +1330,8 @@ describe("Repo", () => {
|
|
|
1418
1330
|
|
|
1419
1331
|
const aliceHandle = aliceRepo.create<TestDoc>()
|
|
1420
1332
|
|
|
1421
|
-
const bobHandle = bobRepo.find(aliceHandle.url)
|
|
1422
|
-
const charlieHandle = charlieRepo.find(aliceHandle.url)
|
|
1333
|
+
const bobHandle = await bobRepo.find(aliceHandle.url)
|
|
1334
|
+
const charlieHandle = await charlieRepo.find(aliceHandle.url)
|
|
1423
1335
|
|
|
1424
1336
|
// Alice should not receive her own ephemeral message
|
|
1425
1337
|
aliceHandle.on("ephemeral-message", () => {
|
|
@@ -1457,9 +1369,8 @@ describe("Repo", () => {
|
|
|
1457
1369
|
// pause to let the sync happen
|
|
1458
1370
|
await pause(50)
|
|
1459
1371
|
|
|
1460
|
-
const charlieHandle = charlieRepo.find(handle2.url)
|
|
1461
|
-
|
|
1462
|
-
assert.deepStrictEqual(charlieHandle.docSync(), { foo: "bar" })
|
|
1372
|
+
const charlieHandle = await charlieRepo.find(handle2.url)
|
|
1373
|
+
assert.deepStrictEqual(charlieHandle.doc(), { foo: "bar" })
|
|
1463
1374
|
|
|
1464
1375
|
teardown()
|
|
1465
1376
|
})
|
|
@@ -1476,9 +1387,8 @@ describe("Repo", () => {
|
|
|
1476
1387
|
// pause to let the sync happen
|
|
1477
1388
|
await pause(50)
|
|
1478
1389
|
|
|
1479
|
-
const charlieHandle = charlieRepo.find(handle2.url)
|
|
1480
|
-
|
|
1481
|
-
assert.deepStrictEqual(charlieHandle.docSync(), { foo: "bar" })
|
|
1390
|
+
const charlieHandle = await charlieRepo.find(handle2.url)
|
|
1391
|
+
assert.deepStrictEqual(charlieHandle.doc(), { foo: "bar" })
|
|
1482
1392
|
|
|
1483
1393
|
// now make a change to doc2 on bobs side and merge it into doc1
|
|
1484
1394
|
handle2.change(d => {
|
|
@@ -1489,8 +1399,7 @@ describe("Repo", () => {
|
|
|
1489
1399
|
// wait for the network to do it's thang
|
|
1490
1400
|
await pause(350)
|
|
1491
1401
|
|
|
1492
|
-
|
|
1493
|
-
assert.deepStrictEqual(charlieHandle.docSync(), { foo: "baz" })
|
|
1402
|
+
assert.deepStrictEqual(charlieHandle.doc(), { foo: "baz" })
|
|
1494
1403
|
|
|
1495
1404
|
teardown()
|
|
1496
1405
|
})
|
|
@@ -1525,9 +1434,9 @@ describe("Repo", () => {
|
|
|
1525
1434
|
eventPromise(client.networkSubsystem, "peer"),
|
|
1526
1435
|
])
|
|
1527
1436
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1437
|
+
await expect(async () => {
|
|
1438
|
+
const clientDoc = await client.find(doc.url)
|
|
1439
|
+
}).rejects.toThrow(/Document (.*) is unavailable/)
|
|
1531
1440
|
|
|
1532
1441
|
const openDocs = Object.keys(server.metrics().documents).length
|
|
1533
1442
|
assert.deepEqual(openDocs, 0)
|
|
@@ -1547,8 +1456,8 @@ describe("Repo heads-in-URLs functionality", () => {
|
|
|
1547
1456
|
const { repo, handle } = setup()
|
|
1548
1457
|
const heads = handle.heads()!
|
|
1549
1458
|
const url = stringifyAutomergeUrl({ documentId: handle.documentId, heads })
|
|
1550
|
-
const view = repo.find(url)
|
|
1551
|
-
expect(view.
|
|
1459
|
+
const view = await repo.find(url)
|
|
1460
|
+
expect(view.doc()).toEqual({ title: "Hello World" })
|
|
1552
1461
|
})
|
|
1553
1462
|
|
|
1554
1463
|
it("returns a view, not the actual handle, when finding by URL with heads", async () => {
|
|
@@ -1556,35 +1465,35 @@ describe("Repo heads-in-URLs functionality", () => {
|
|
|
1556
1465
|
const heads = handle.heads()!
|
|
1557
1466
|
await handle.change((doc: any) => (doc.title = "Changed"))
|
|
1558
1467
|
const url = stringifyAutomergeUrl({ documentId: handle.documentId, heads })
|
|
1559
|
-
const view = repo.find(url)
|
|
1560
|
-
expect(view.
|
|
1561
|
-
expect(handle.
|
|
1468
|
+
const view = await repo.find(url)
|
|
1469
|
+
expect(view.doc()).toEqual({ title: "Hello World" })
|
|
1470
|
+
expect(handle.doc()).toEqual({ title: "Changed" })
|
|
1562
1471
|
})
|
|
1563
1472
|
|
|
1564
1473
|
it("changes to a document view do not affect the original", async () => {
|
|
1565
1474
|
const { repo, handle } = setup()
|
|
1566
1475
|
const heads = handle.heads()!
|
|
1567
1476
|
const url = stringifyAutomergeUrl({ documentId: handle.documentId, heads })
|
|
1568
|
-
const view = repo.find(url)
|
|
1477
|
+
const view = await repo.find(url)
|
|
1569
1478
|
expect(() =>
|
|
1570
1479
|
view.change((doc: any) => (doc.title = "Changed in View"))
|
|
1571
1480
|
).toThrow()
|
|
1572
|
-
expect(handle.
|
|
1481
|
+
expect(handle.doc()).toEqual({ title: "Hello World" })
|
|
1573
1482
|
})
|
|
1574
1483
|
|
|
1575
1484
|
it("document views are read-only", async () => {
|
|
1576
1485
|
const { repo, handle } = setup()
|
|
1577
1486
|
const heads = handle.heads()!
|
|
1578
1487
|
const url = stringifyAutomergeUrl({ documentId: handle.documentId, heads })
|
|
1579
|
-
const view = repo.find(url)
|
|
1488
|
+
const view = await repo.find(url)
|
|
1580
1489
|
expect(() => view.change((doc: any) => (doc.title = "Changed"))).toThrow()
|
|
1581
1490
|
})
|
|
1582
1491
|
|
|
1583
1492
|
it("finds the latest document when given a URL without heads", async () => {
|
|
1584
1493
|
const { repo, handle } = setup()
|
|
1585
1494
|
await handle.change((doc: any) => (doc.title = "Changed"))
|
|
1586
|
-
const found = repo.find(handle.url)
|
|
1587
|
-
expect(found.
|
|
1495
|
+
const found = await repo.find(handle.url)
|
|
1496
|
+
expect(found.doc()).toEqual({ title: "Changed" })
|
|
1588
1497
|
})
|
|
1589
1498
|
|
|
1590
1499
|
it("getHeadsFromUrl returns heads array if present or undefined", () => {
|
|
@@ -1640,6 +1549,50 @@ describe("Repo heads-in-URLs functionality", () => {
|
|
|
1640
1549
|
})
|
|
1641
1550
|
})
|
|
1642
1551
|
|
|
1552
|
+
describe("Repo.find() abort behavior", () => {
|
|
1553
|
+
it("aborts immediately if signal is already aborted", async () => {
|
|
1554
|
+
const repo = new Repo()
|
|
1555
|
+
const controller = new AbortController()
|
|
1556
|
+
controller.abort()
|
|
1557
|
+
|
|
1558
|
+
await expect(
|
|
1559
|
+
repo.find(generateAutomergeUrl(), { signal: controller.signal })
|
|
1560
|
+
).rejects.toThrow("Operation aborted")
|
|
1561
|
+
})
|
|
1562
|
+
|
|
1563
|
+
it("can abort while waiting for ready state", async () => {
|
|
1564
|
+
// Create a repo with no network adapters so document can't become ready
|
|
1565
|
+
const repo = new Repo()
|
|
1566
|
+
const url = generateAutomergeUrl()
|
|
1567
|
+
|
|
1568
|
+
const controller = new AbortController()
|
|
1569
|
+
|
|
1570
|
+
// Start find and abort after a moment
|
|
1571
|
+
const findPromise = repo.find(url, { signal: controller.signal })
|
|
1572
|
+
controller.abort()
|
|
1573
|
+
|
|
1574
|
+
await expect(findPromise).rejects.toThrow("Operation aborted")
|
|
1575
|
+
await expect(findPromise).rejects.not.toThrow("unavailable")
|
|
1576
|
+
})
|
|
1577
|
+
|
|
1578
|
+
it("returns handle immediately when allow unavailable is true, even with abort signal", async () => {
|
|
1579
|
+
const repo = new Repo()
|
|
1580
|
+
const controller = new AbortController()
|
|
1581
|
+
const url = generateAutomergeUrl()
|
|
1582
|
+
|
|
1583
|
+
const handle = await repo.find(url, {
|
|
1584
|
+
allowableStates: ["unavailable"],
|
|
1585
|
+
signal: controller.signal,
|
|
1586
|
+
})
|
|
1587
|
+
|
|
1588
|
+
expect(handle).toBeDefined()
|
|
1589
|
+
|
|
1590
|
+
// Abort shouldn't affect the result since we skipped ready
|
|
1591
|
+
controller.abort()
|
|
1592
|
+
expect(handle.url).toBe(url)
|
|
1593
|
+
})
|
|
1594
|
+
})
|
|
1595
|
+
|
|
1643
1596
|
const warn = console.warn
|
|
1644
1597
|
const NO_OP = () => {}
|
|
1645
1598
|
|