@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.
- package/README.md +8 -8
- package/dist/AutomergeUrl.d.ts +17 -5
- package/dist/AutomergeUrl.d.ts.map +1 -1
- package/dist/AutomergeUrl.js +71 -24
- package/dist/DocHandle.d.ts +33 -41
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +105 -66
- package/dist/FindProgress.d.ts +30 -0
- package/dist/FindProgress.d.ts.map +1 -0
- package/dist/FindProgress.js +1 -0
- package/dist/RemoteHeadsSubscriptions.d.ts +4 -5
- package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
- package/dist/RemoteHeadsSubscriptions.js +4 -1
- package/dist/Repo.d.ts +24 -5
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +355 -169
- package/dist/helpers/abortable.d.ts +36 -0
- package/dist/helpers/abortable.d.ts.map +1 -0
- package/dist/helpers/abortable.js +47 -0
- package/dist/helpers/arraysAreEqual.d.ts.map +1 -1
- package/dist/helpers/bufferFromHex.d.ts +3 -0
- package/dist/helpers/bufferFromHex.d.ts.map +1 -0
- package/dist/helpers/bufferFromHex.js +13 -0
- package/dist/helpers/debounce.d.ts.map +1 -1
- package/dist/helpers/eventPromise.d.ts.map +1 -1
- package/dist/helpers/headsAreSame.d.ts +2 -2
- package/dist/helpers/headsAreSame.d.ts.map +1 -1
- package/dist/helpers/mergeArrays.d.ts +1 -1
- package/dist/helpers/mergeArrays.d.ts.map +1 -1
- package/dist/helpers/pause.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +13 -13
- package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/storage-adapter-tests.js +6 -9
- package/dist/helpers/throttle.d.ts.map +1 -1
- package/dist/helpers/withTimeout.d.ts.map +1 -1
- package/dist/index.d.ts +35 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -6
- package/dist/network/NetworkSubsystem.d.ts +0 -1
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +0 -3
- package/dist/network/messages.d.ts +1 -7
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/network/messages.js +1 -2
- package/dist/storage/StorageAdapter.d.ts +0 -9
- package/dist/storage/StorageAdapter.d.ts.map +1 -1
- package/dist/storage/StorageAdapter.js +0 -33
- package/dist/storage/StorageSubsystem.d.ts +6 -2
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +131 -37
- package/dist/storage/keyHash.d.ts +1 -1
- package/dist/storage/keyHash.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -4
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +32 -26
- package/dist/synchronizer/DocSynchronizer.d.ts +8 -8
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +205 -79
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/fuzz/fuzz.ts +3 -3
- package/package.json +4 -5
- package/src/AutomergeUrl.ts +101 -26
- package/src/DocHandle.ts +158 -77
- package/src/FindProgress.ts +48 -0
- package/src/RemoteHeadsSubscriptions.ts +11 -9
- package/src/Repo.ts +465 -180
- package/src/helpers/abortable.ts +62 -0
- package/src/helpers/bufferFromHex.ts +14 -0
- package/src/helpers/headsAreSame.ts +2 -2
- package/src/helpers/tests/network-adapter-tests.ts +14 -13
- package/src/helpers/tests/storage-adapter-tests.ts +13 -24
- package/src/index.ts +57 -38
- package/src/network/NetworkSubsystem.ts +0 -4
- package/src/network/messages.ts +2 -11
- package/src/storage/StorageAdapter.ts +0 -42
- package/src/storage/StorageSubsystem.ts +155 -45
- package/src/storage/keyHash.ts +1 -1
- package/src/synchronizer/CollectionSynchronizer.ts +42 -29
- package/src/synchronizer/DocSynchronizer.ts +263 -89
- package/src/types.ts +4 -1
- package/test/AutomergeUrl.test.ts +130 -0
- package/test/CollectionSynchronizer.test.ts +6 -8
- package/test/DocHandle.test.ts +161 -77
- package/test/DocSynchronizer.test.ts +11 -9
- package/test/RemoteHeadsSubscriptions.test.ts +1 -1
- package/test/Repo.test.ts +406 -341
- package/test/StorageSubsystem.test.ts +95 -20
- package/test/remoteHeads.test.ts +28 -13
- package/dist/CollectionHandle.d.ts +0 -14
- package/dist/CollectionHandle.d.ts.map +0 -1
- package/dist/CollectionHandle.js +0 -37
- package/dist/DocUrl.d.ts +0 -47
- package/dist/DocUrl.d.ts.map +0 -1
- package/dist/DocUrl.js +0 -72
- package/dist/EphemeralData.d.ts +0 -20
- package/dist/EphemeralData.d.ts.map +0 -1
- package/dist/EphemeralData.js +0 -1
- package/dist/ferigan.d.ts +0 -51
- package/dist/ferigan.d.ts.map +0 -1
- package/dist/ferigan.js +0 -98
- package/dist/src/DocHandle.d.ts +0 -182
- package/dist/src/DocHandle.d.ts.map +0 -1
- package/dist/src/DocHandle.js +0 -405
- package/dist/src/DocUrl.d.ts +0 -49
- package/dist/src/DocUrl.d.ts.map +0 -1
- package/dist/src/DocUrl.js +0 -72
- package/dist/src/EphemeralData.d.ts +0 -19
- package/dist/src/EphemeralData.d.ts.map +0 -1
- package/dist/src/EphemeralData.js +0 -1
- package/dist/src/Repo.d.ts +0 -74
- package/dist/src/Repo.d.ts.map +0 -1
- package/dist/src/Repo.js +0 -208
- package/dist/src/helpers/arraysAreEqual.d.ts +0 -2
- package/dist/src/helpers/arraysAreEqual.d.ts.map +0 -1
- package/dist/src/helpers/arraysAreEqual.js +0 -2
- package/dist/src/helpers/cbor.d.ts +0 -4
- package/dist/src/helpers/cbor.d.ts.map +0 -1
- package/dist/src/helpers/cbor.js +0 -8
- package/dist/src/helpers/eventPromise.d.ts +0 -11
- package/dist/src/helpers/eventPromise.d.ts.map +0 -1
- package/dist/src/helpers/eventPromise.js +0 -7
- package/dist/src/helpers/headsAreSame.d.ts +0 -2
- package/dist/src/helpers/headsAreSame.d.ts.map +0 -1
- package/dist/src/helpers/headsAreSame.js +0 -4
- package/dist/src/helpers/mergeArrays.d.ts +0 -2
- package/dist/src/helpers/mergeArrays.d.ts.map +0 -1
- package/dist/src/helpers/mergeArrays.js +0 -15
- package/dist/src/helpers/pause.d.ts +0 -6
- package/dist/src/helpers/pause.d.ts.map +0 -1
- package/dist/src/helpers/pause.js +0 -10
- package/dist/src/helpers/tests/network-adapter-tests.d.ts +0 -21
- package/dist/src/helpers/tests/network-adapter-tests.d.ts.map +0 -1
- package/dist/src/helpers/tests/network-adapter-tests.js +0 -122
- package/dist/src/helpers/withTimeout.d.ts +0 -12
- package/dist/src/helpers/withTimeout.d.ts.map +0 -1
- package/dist/src/helpers/withTimeout.js +0 -24
- package/dist/src/index.d.ts +0 -53
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -40
- package/dist/src/network/NetworkAdapter.d.ts +0 -26
- package/dist/src/network/NetworkAdapter.d.ts.map +0 -1
- package/dist/src/network/NetworkAdapter.js +0 -4
- package/dist/src/network/NetworkSubsystem.d.ts +0 -23
- package/dist/src/network/NetworkSubsystem.d.ts.map +0 -1
- package/dist/src/network/NetworkSubsystem.js +0 -120
- package/dist/src/network/messages.d.ts +0 -85
- package/dist/src/network/messages.d.ts.map +0 -1
- package/dist/src/network/messages.js +0 -23
- package/dist/src/storage/StorageAdapter.d.ts +0 -14
- package/dist/src/storage/StorageAdapter.d.ts.map +0 -1
- package/dist/src/storage/StorageAdapter.js +0 -1
- package/dist/src/storage/StorageSubsystem.d.ts +0 -12
- package/dist/src/storage/StorageSubsystem.d.ts.map +0 -1
- package/dist/src/storage/StorageSubsystem.js +0 -145
- package/dist/src/synchronizer/CollectionSynchronizer.d.ts +0 -25
- package/dist/src/synchronizer/CollectionSynchronizer.d.ts.map +0 -1
- package/dist/src/synchronizer/CollectionSynchronizer.js +0 -106
- package/dist/src/synchronizer/DocSynchronizer.d.ts +0 -29
- package/dist/src/synchronizer/DocSynchronizer.d.ts.map +0 -1
- package/dist/src/synchronizer/DocSynchronizer.js +0 -263
- package/dist/src/synchronizer/Synchronizer.d.ts +0 -9
- package/dist/src/synchronizer/Synchronizer.d.ts.map +0 -1
- package/dist/src/synchronizer/Synchronizer.js +0 -2
- package/dist/src/types.d.ts +0 -16
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -1
- package/dist/test/CollectionSynchronizer.test.d.ts +0 -2
- package/dist/test/CollectionSynchronizer.test.d.ts.map +0 -1
- package/dist/test/CollectionSynchronizer.test.js +0 -57
- package/dist/test/DocHandle.test.d.ts +0 -2
- package/dist/test/DocHandle.test.d.ts.map +0 -1
- package/dist/test/DocHandle.test.js +0 -238
- package/dist/test/DocSynchronizer.test.d.ts +0 -2
- package/dist/test/DocSynchronizer.test.d.ts.map +0 -1
- package/dist/test/DocSynchronizer.test.js +0 -111
- package/dist/test/Network.test.d.ts +0 -2
- package/dist/test/Network.test.d.ts.map +0 -1
- package/dist/test/Network.test.js +0 -11
- package/dist/test/Repo.test.d.ts +0 -2
- package/dist/test/Repo.test.d.ts.map +0 -1
- package/dist/test/Repo.test.js +0 -568
- package/dist/test/StorageSubsystem.test.d.ts +0 -2
- package/dist/test/StorageSubsystem.test.d.ts.map +0 -1
- package/dist/test/StorageSubsystem.test.js +0 -56
- package/dist/test/helpers/DummyNetworkAdapter.d.ts +0 -9
- package/dist/test/helpers/DummyNetworkAdapter.d.ts.map +0 -1
- package/dist/test/helpers/DummyNetworkAdapter.js +0 -15
- package/dist/test/helpers/DummyStorageAdapter.d.ts +0 -16
- package/dist/test/helpers/DummyStorageAdapter.d.ts.map +0 -1
- package/dist/test/helpers/DummyStorageAdapter.js +0 -33
- package/dist/test/helpers/generate-large-object.d.ts +0 -5
- package/dist/test/helpers/generate-large-object.d.ts.map +0 -1
- package/dist/test/helpers/generate-large-object.js +0 -9
- package/dist/test/helpers/getRandomItem.d.ts +0 -2
- package/dist/test/helpers/getRandomItem.d.ts.map +0 -1
- package/dist/test/helpers/getRandomItem.js +0 -4
- package/dist/test/types.d.ts +0 -4
- package/dist/test/types.d.ts.map +0 -1
- package/dist/test/types.js +0 -1
- package/src/CollectionHandle.ts +0 -54
- package/src/ferigan.ts +0 -184
package/src/DocHandle.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { next as A } from "@automerge/automerge/slim"
|
|
2
2
|
import debug from "debug"
|
|
3
3
|
import { EventEmitter } from "eventemitter3"
|
|
4
4
|
import { assertEvent, assign, createActor, setup, waitFor } from "xstate"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
decodeHeads,
|
|
7
|
+
encodeHeads,
|
|
8
|
+
stringifyAutomergeUrl,
|
|
9
|
+
} from "./AutomergeUrl.js"
|
|
6
10
|
import { encode } from "./helpers/cbor.js"
|
|
7
11
|
import { headsAreSame } from "./helpers/headsAreSame.js"
|
|
8
12
|
import { withTimeout } from "./helpers/withTimeout.js"
|
|
9
|
-
import type { AutomergeUrl, DocumentId, PeerId } from "./types.js"
|
|
13
|
+
import type { AutomergeUrl, DocumentId, PeerId, UrlHeads } from "./types.js"
|
|
10
14
|
import { StorageId } from "./storage/types.js"
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -28,6 +32,9 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
28
32
|
/** The XState actor running our state machine. */
|
|
29
33
|
#machine
|
|
30
34
|
|
|
35
|
+
/** If set, this handle will only show the document at these heads */
|
|
36
|
+
#fixedHeads?: UrlHeads
|
|
37
|
+
|
|
31
38
|
/** The last known state of our document. */
|
|
32
39
|
#prevDocState: T = A.init<T>()
|
|
33
40
|
|
|
@@ -36,7 +43,10 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
36
43
|
#timeoutDelay = 60_000
|
|
37
44
|
|
|
38
45
|
/** A dictionary mapping each peer to the last heads we know they have. */
|
|
39
|
-
#remoteHeads: Record<StorageId,
|
|
46
|
+
#remoteHeads: Record<StorageId, UrlHeads> = {}
|
|
47
|
+
|
|
48
|
+
/** Cache for view handles, keyed by the stringified heads */
|
|
49
|
+
#viewCache: Map<string, DocHandle<T>> = new Map()
|
|
40
50
|
|
|
41
51
|
/** @hidden */
|
|
42
52
|
constructor(
|
|
@@ -49,6 +59,10 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
49
59
|
this.#timeoutDelay = options.timeoutDelay
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
if ("heads" in options) {
|
|
63
|
+
this.#fixedHeads = options.heads
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
const doc = A.init<T>()
|
|
53
67
|
|
|
54
68
|
this.#log = debug(`automerge-repo:dochandle:${this.documentId.slice(0, 5)}`)
|
|
@@ -72,12 +86,12 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
72
86
|
this.emit("delete", { handle: this })
|
|
73
87
|
return { doc: A.init() }
|
|
74
88
|
}),
|
|
89
|
+
onUnavailable: assign(() => {
|
|
90
|
+
return { doc: A.init() }
|
|
91
|
+
}),
|
|
75
92
|
onUnload: assign(() => {
|
|
76
93
|
return { doc: A.init() }
|
|
77
94
|
}),
|
|
78
|
-
onUnavailable: () => {
|
|
79
|
-
this.emit("unavailable", { handle: this })
|
|
80
|
-
},
|
|
81
95
|
},
|
|
82
96
|
}).createMachine({
|
|
83
97
|
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAYgFUAFAEQEEAVAUQG0AGAXUVAAcB7WXAC64e+TiAAeiAOwAOAKwA6ACxSAzKqks1ATjlTdAGhABPRAFolAJksKN2y1KtKAbFLla5AX09G0WPISkVAwAMgyMrBxIILz8QiJikggAjCzOijKqLEqqybJyLizaRqYIFpbJtro5Uo7J2o5S3r4YOATECrgQADZgJADCAEoM9MzsYrGCwqLRSeoyCtra8pa5adquySXmDjY5ac7JljLJeepKzSB+bYGdPX0AYgCSAHJUkRN8UwmziM7HCgqyVcUnqcmScmcMm2ZV2yiyzkOx1OalUFx8V1aAQ63R46AgBCgJGGAEUyAwAMp0D7RSbxGagJKHFgKOSWJTJGRSCosCpKaEmRCqbQKU5yXINeTaer6LwY67YogKXH4wkkKgAeX6AH1hjQqABNGncL70xKIJQ5RY5BHOJag6wwpRyEWImQVeT1aWrVSXBXtJUqgn4Ik0ADqNCedG1L3CYY1gwA0saYqbpuaEG4pKLksKpFDgcsCjDhTnxTKpTLdH6sQGFOgAO7oKYhl5gAQNngAJwA1iRY3R40ndSNDSm6enfpm5BkWAVkvy7bpuTCKq7ndZnfVeSwuTX-HWu2AAI4AVzgQhD6q12rILxoADVIyEaAAhMLjtM-RmIE4LVSQi4nLLDIGzOCWwLKA0cgyLBoFWNy+43B0R5nheaqajqepjuMtJfgyEh-FoixqMCoKqOyhzgYKCDOq6UIeuCSxHOoSGKgop74OgABuzbdOgABGvTXlho5GrhJpxJOP4pLulT6KoMhpJY2hzsWNF0QobqMV6LG+pc+A8BAcBiP6gSfFJ36EQgKksksKxrHamwwmY7gLKB85QjBzoAWxdZdL0FnfARST8ooLC7qoTnWBU4pyC5ViVMKBQaHUDQuM4fm3EGhJBWaU7-CysEAUp3LpEpWw0WYRw2LmqzgqciIsCxWUdI2zaXlAbYdt2PZ5dJ1n5jY2iJY1ikOIcMJHCyUWHC62hRZkUVNPKta3Kh56wJ1-VWUyzhFc64JWJCtQNBBzhQW4cHwbsrVKpxPF8YJgV4ZZIWIKkiKiiNSkqZYWjzCWaQ5hFh0AcCuR3QoR74qUknBRmzholpv3OkpRQNNRpTzaKTWKbIWR5FDxm9AIkA7e9skUYCWayLILBZGoLkUSKbIyIdpxHPoyTeN4QA */
|
|
@@ -176,7 +190,10 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
176
190
|
#checkForChanges(before: A.Doc<T>, after: A.Doc<T>) {
|
|
177
191
|
const beforeHeads = A.getHeads(before)
|
|
178
192
|
const afterHeads = A.getHeads(after)
|
|
179
|
-
const docChanged = !headsAreSame(
|
|
193
|
+
const docChanged = !headsAreSame(
|
|
194
|
+
encodeHeads(afterHeads),
|
|
195
|
+
encodeHeads(beforeHeads)
|
|
196
|
+
)
|
|
180
197
|
if (docChanged) {
|
|
181
198
|
this.emit("heads-changed", { handle: this, doc: after })
|
|
182
199
|
|
|
@@ -202,7 +219,10 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
202
219
|
/** Our documentId in Automerge URL form.
|
|
203
220
|
*/
|
|
204
221
|
get url(): AutomergeUrl {
|
|
205
|
-
return stringifyAutomergeUrl({
|
|
222
|
+
return stringifyAutomergeUrl({
|
|
223
|
+
documentId: this.documentId,
|
|
224
|
+
heads: this.#fixedHeads,
|
|
225
|
+
})
|
|
206
226
|
}
|
|
207
227
|
|
|
208
228
|
/**
|
|
@@ -259,42 +279,28 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
259
279
|
}
|
|
260
280
|
|
|
261
281
|
/**
|
|
262
|
-
*
|
|
282
|
+
* Returns the current state of the Automerge document this handle manages.
|
|
283
|
+
*
|
|
284
|
+
* @returns the current document
|
|
285
|
+
* @throws on deleted and unavailable documents
|
|
263
286
|
*
|
|
264
|
-
* This is the recommended way to access a handle's document. Note that this waits for the handle
|
|
265
|
-
* to be ready if necessary. If loading (or synchronization) fails, this will never resolve.
|
|
266
287
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
// wait for the document to enter one of the desired states
|
|
273
|
-
await this.#statePromise(awaitStates)
|
|
274
|
-
} catch (error) {
|
|
275
|
-
// if we timed out, return undefined
|
|
276
|
-
return undefined
|
|
288
|
+
doc() {
|
|
289
|
+
if (!this.isReady()) throw new Error("DocHandle is not ready")
|
|
290
|
+
if (this.#fixedHeads) {
|
|
291
|
+
return A.view(this.#doc, decodeHeads(this.#fixedHeads))
|
|
277
292
|
}
|
|
278
|
-
|
|
279
|
-
return !this.isUnavailable() ? this.#doc : undefined
|
|
293
|
+
return this.#doc
|
|
280
294
|
}
|
|
281
295
|
|
|
282
296
|
/**
|
|
283
|
-
* Synchronously returns the current state of the Automerge document this handle manages, or
|
|
284
|
-
* undefined. Consider using `await handle.doc()` instead. Check `isReady()`, or use `whenReady()`
|
|
285
|
-
* if you want to make sure loading is complete first.
|
|
286
|
-
*
|
|
287
|
-
* Not to be confused with the SyncState of the document, which describes the state of the
|
|
288
|
-
* synchronization process.
|
|
289
|
-
*
|
|
290
|
-
* Note that `undefined` is not a valid Automerge document, so the return from this function is
|
|
291
|
-
* unambigous.
|
|
292
297
|
*
|
|
293
|
-
* @
|
|
294
|
-
*/
|
|
298
|
+
* @deprecated */
|
|
295
299
|
docSync() {
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
console.warn(
|
|
301
|
+
"docSync is deprecated. Use doc() instead. This function will be removed as part of the 2.0 release."
|
|
302
|
+
)
|
|
303
|
+
return this.doc()
|
|
298
304
|
}
|
|
299
305
|
|
|
300
306
|
/**
|
|
@@ -302,11 +308,12 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
302
308
|
* This precisely defines the state of a document.
|
|
303
309
|
* @returns the current document's heads, or undefined if the document is not ready
|
|
304
310
|
*/
|
|
305
|
-
heads():
|
|
306
|
-
if (!this.isReady())
|
|
307
|
-
|
|
311
|
+
heads(): UrlHeads {
|
|
312
|
+
if (!this.isReady()) throw new Error("DocHandle is not ready")
|
|
313
|
+
if (this.#fixedHeads) {
|
|
314
|
+
return this.#fixedHeads
|
|
308
315
|
}
|
|
309
|
-
return A.getHeads(this.#doc)
|
|
316
|
+
return encodeHeads(A.getHeads(this.#doc))
|
|
310
317
|
}
|
|
311
318
|
|
|
312
319
|
begin() {
|
|
@@ -314,9 +321,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
314
321
|
}
|
|
315
322
|
|
|
316
323
|
/**
|
|
317
|
-
*
|
|
318
|
-
* by the `heads` passed in. The return value is the same type as docSync() and will return
|
|
319
|
-
* undefined if the object hasn't finished loading.
|
|
324
|
+
* Returns an array of all past "heads" for the document in topological order.
|
|
320
325
|
*
|
|
321
326
|
* @remarks
|
|
322
327
|
* A point-in-time in an automerge document is an *array* of heads since there may be
|
|
@@ -325,20 +330,22 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
325
330
|
* history views would be quite large under concurrency (every thing in each branch against each other).
|
|
326
331
|
* There might be a clever way to think about this, but we haven't found it yet, so for now at least
|
|
327
332
|
* we present a single traversable view which excludes concurrency.
|
|
328
|
-
* @returns The individual heads for every change in the document.
|
|
333
|
+
* @returns UrlHeads[] - The individual heads for every change in the document. Each item is a tagged string[1].
|
|
329
334
|
*/
|
|
330
|
-
history():
|
|
335
|
+
history(): UrlHeads[] | undefined {
|
|
331
336
|
if (!this.isReady()) {
|
|
332
337
|
return undefined
|
|
333
338
|
}
|
|
334
339
|
// This just returns all the heads as individual strings.
|
|
335
340
|
|
|
336
|
-
return A.topoHistoryTraversal(this.#doc).map(h =>
|
|
341
|
+
return A.topoHistoryTraversal(this.#doc).map(h =>
|
|
342
|
+
encodeHeads([h])
|
|
343
|
+
) as UrlHeads[]
|
|
337
344
|
}
|
|
338
345
|
|
|
339
346
|
/**
|
|
340
347
|
* Creates a fixed "view" of an automerge document at the given point in time represented
|
|
341
|
-
* by the `heads` passed in. The return value is the same type as
|
|
348
|
+
* by the `heads` passed in. The return value is the same type as doc() and will return
|
|
342
349
|
* undefined if the object hasn't finished loading.
|
|
343
350
|
*
|
|
344
351
|
* @remarks
|
|
@@ -346,13 +353,37 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
346
353
|
* of Automerge doesn't check types at runtime, so if you go back to an old set of heads
|
|
347
354
|
* that doesn't match the heads here, Typescript will not save you.
|
|
348
355
|
*
|
|
349
|
-
* @
|
|
356
|
+
* @argument heads - The heads to view the document at. See history().
|
|
357
|
+
* @returns DocHandle<T> at the time of `heads`
|
|
350
358
|
*/
|
|
351
|
-
view(heads:
|
|
359
|
+
view(heads: UrlHeads): DocHandle<T> {
|
|
352
360
|
if (!this.isReady()) {
|
|
353
|
-
|
|
361
|
+
throw new Error(
|
|
362
|
+
`DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before calling view().`
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Create a cache key from the heads
|
|
367
|
+
const cacheKey = JSON.stringify(heads)
|
|
368
|
+
|
|
369
|
+
// Check if we have a cached handle for these heads
|
|
370
|
+
const cachedHandle = this.#viewCache.get(cacheKey)
|
|
371
|
+
if (cachedHandle) {
|
|
372
|
+
return cachedHandle
|
|
354
373
|
}
|
|
355
|
-
|
|
374
|
+
|
|
375
|
+
// Create a new handle with the same documentId but fixed heads
|
|
376
|
+
const handle = new DocHandle<T>(this.documentId, {
|
|
377
|
+
heads,
|
|
378
|
+
timeoutDelay: this.#timeoutDelay,
|
|
379
|
+
})
|
|
380
|
+
handle.update(() => A.clone(this.#doc))
|
|
381
|
+
handle.doneLoading()
|
|
382
|
+
|
|
383
|
+
// Store in cache
|
|
384
|
+
this.#viewCache.set(cacheKey, handle)
|
|
385
|
+
|
|
386
|
+
return handle
|
|
356
387
|
}
|
|
357
388
|
|
|
358
389
|
/**
|
|
@@ -360,19 +391,46 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
360
391
|
* if applied.
|
|
361
392
|
*
|
|
362
393
|
* @remarks
|
|
363
|
-
* We allow specifying
|
|
364
|
-
*
|
|
394
|
+
* We allow specifying either:
|
|
395
|
+
* - Two sets of heads to compare directly
|
|
396
|
+
* - A single set of heads to compare against our current heads
|
|
397
|
+
* - Another DocHandle to compare against (which must share history with this document)
|
|
365
398
|
*
|
|
366
|
-
* @
|
|
399
|
+
* @throws Error if the documents don't share history or if either document is not ready
|
|
400
|
+
* @returns Automerge patches that go from one document state to the other
|
|
367
401
|
*/
|
|
368
|
-
diff(first:
|
|
402
|
+
diff(first: UrlHeads | DocHandle<T>, second?: UrlHeads): A.Patch[] {
|
|
369
403
|
if (!this.isReady()) {
|
|
370
|
-
|
|
404
|
+
throw new Error(
|
|
405
|
+
`DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before calling diff().`
|
|
406
|
+
)
|
|
371
407
|
}
|
|
372
|
-
|
|
373
|
-
const
|
|
408
|
+
|
|
409
|
+
const doc = this.#doc
|
|
410
|
+
if (!doc) throw new Error("Document not available")
|
|
411
|
+
|
|
412
|
+
// If first argument is a DocHandle
|
|
413
|
+
if (first instanceof DocHandle) {
|
|
414
|
+
if (!first.isReady()) {
|
|
415
|
+
throw new Error("Cannot diff against a handle that isn't ready")
|
|
416
|
+
}
|
|
417
|
+
const otherHeads = first.heads()
|
|
418
|
+
if (!otherHeads) throw new Error("Other document's heads not available")
|
|
419
|
+
|
|
420
|
+
// Create a temporary merged doc to verify shared history and compute diff
|
|
421
|
+
const mergedDoc = A.merge(A.clone(doc), first.doc()!)
|
|
422
|
+
// Use the merged doc to compute the diff
|
|
423
|
+
return A.diff(
|
|
424
|
+
mergedDoc,
|
|
425
|
+
decodeHeads(this.heads()!),
|
|
426
|
+
decodeHeads(otherHeads)
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Otherwise treat as heads
|
|
431
|
+
const from = second ? first : ((this.heads() || []) as UrlHeads)
|
|
374
432
|
const to = second ? second : first
|
|
375
|
-
return A.diff(
|
|
433
|
+
return A.diff(doc, decodeHeads(from), decodeHeads(to))
|
|
376
434
|
}
|
|
377
435
|
|
|
378
436
|
/**
|
|
@@ -390,11 +448,15 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
390
448
|
if (!this.isReady()) {
|
|
391
449
|
return undefined
|
|
392
450
|
}
|
|
451
|
+
|
|
393
452
|
if (!change) {
|
|
394
453
|
change = this.heads()![0]
|
|
395
454
|
}
|
|
396
455
|
// we return undefined instead of null by convention in this API
|
|
397
|
-
return
|
|
456
|
+
return (
|
|
457
|
+
A.inspectChange(this.#doc, decodeHeads([change] as UrlHeads)[0]) ||
|
|
458
|
+
undefined
|
|
459
|
+
)
|
|
398
460
|
}
|
|
399
461
|
|
|
400
462
|
/**
|
|
@@ -417,16 +479,16 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
417
479
|
}
|
|
418
480
|
|
|
419
481
|
/**
|
|
420
|
-
* Called by the repo
|
|
482
|
+
* Called by the repo when a doc handle changes or we receive new remote heads.
|
|
421
483
|
* @hidden
|
|
422
484
|
*/
|
|
423
|
-
setRemoteHeads(storageId: StorageId, heads:
|
|
485
|
+
setRemoteHeads(storageId: StorageId, heads: UrlHeads) {
|
|
424
486
|
this.#remoteHeads[storageId] = heads
|
|
425
487
|
this.emit("remote-heads", { storageId, heads })
|
|
426
488
|
}
|
|
427
489
|
|
|
428
490
|
/** Returns the heads of the storageId. */
|
|
429
|
-
getRemoteHeads(storageId: StorageId):
|
|
491
|
+
getRemoteHeads(storageId: StorageId): UrlHeads | undefined {
|
|
430
492
|
return this.#remoteHeads[storageId]
|
|
431
493
|
}
|
|
432
494
|
|
|
@@ -451,6 +513,13 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
451
513
|
`DocHandle#${this.documentId} is in ${this.state} and not ready. Check \`handle.isReady()\` before accessing the document.`
|
|
452
514
|
)
|
|
453
515
|
}
|
|
516
|
+
|
|
517
|
+
if (this.#fixedHeads) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
454
523
|
this.#machine.send({
|
|
455
524
|
type: UPDATE,
|
|
456
525
|
payload: { callback: doc => A.change(doc, options, callback) },
|
|
@@ -462,22 +531,29 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
462
531
|
* @returns A set of heads representing the concurrent change that was made.
|
|
463
532
|
*/
|
|
464
533
|
changeAt(
|
|
465
|
-
heads:
|
|
534
|
+
heads: UrlHeads,
|
|
466
535
|
callback: A.ChangeFn<T>,
|
|
467
536
|
options: A.ChangeOptions<T> = {}
|
|
468
|
-
):
|
|
537
|
+
): UrlHeads[] | undefined {
|
|
469
538
|
if (!this.isReady()) {
|
|
470
539
|
throw new Error(
|
|
471
540
|
`DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before accessing the document.`
|
|
472
541
|
)
|
|
473
542
|
}
|
|
474
|
-
|
|
543
|
+
if (this.#fixedHeads) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
let resultHeads: UrlHeads | undefined = undefined
|
|
475
549
|
this.#machine.send({
|
|
476
550
|
type: UPDATE,
|
|
477
551
|
payload: {
|
|
478
552
|
callback: doc => {
|
|
479
|
-
const result = A.changeAt(doc, heads, options, callback)
|
|
480
|
-
resultHeads = result.newHeads
|
|
553
|
+
const result = A.changeAt(doc, decodeHeads(heads), options, callback)
|
|
554
|
+
resultHeads = result.newHeads
|
|
555
|
+
? encodeHeads(result.newHeads)
|
|
556
|
+
: undefined
|
|
481
557
|
return result.newDoc
|
|
482
558
|
},
|
|
483
559
|
},
|
|
@@ -502,10 +578,12 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
502
578
|
if (!this.isReady() || !otherHandle.isReady()) {
|
|
503
579
|
throw new Error("Both handles must be ready to merge")
|
|
504
580
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
581
|
+
if (this.#fixedHeads) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`
|
|
584
|
+
)
|
|
508
585
|
}
|
|
586
|
+
const mergingDoc = otherHandle.doc()
|
|
509
587
|
|
|
510
588
|
this.update(doc => {
|
|
511
589
|
return A.merge(doc, mergingDoc)
|
|
@@ -513,14 +591,15 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
513
591
|
}
|
|
514
592
|
|
|
515
593
|
/**
|
|
516
|
-
*
|
|
594
|
+
* Updates the internal state machine to mark the document unavailable.
|
|
517
595
|
* @hidden
|
|
518
596
|
*/
|
|
519
597
|
unavailable() {
|
|
520
598
|
this.#machine.send({ type: DOC_UNAVAILABLE })
|
|
521
599
|
}
|
|
522
600
|
|
|
523
|
-
/**
|
|
601
|
+
/**
|
|
602
|
+
* Called by the repo either when the document is not found in storage.
|
|
524
603
|
* @hidden
|
|
525
604
|
* */
|
|
526
605
|
request() {
|
|
@@ -552,7 +631,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
552
631
|
broadcast(message: unknown) {
|
|
553
632
|
this.emit("ephemeral-message-outbound", {
|
|
554
633
|
handle: this,
|
|
555
|
-
data: encode(message),
|
|
634
|
+
data: new Uint8Array(encode(message)),
|
|
556
635
|
})
|
|
557
636
|
}
|
|
558
637
|
|
|
@@ -577,6 +656,9 @@ export type DocHandleOptions<T> =
|
|
|
577
656
|
| {
|
|
578
657
|
isNew?: false
|
|
579
658
|
|
|
659
|
+
// An optional point in time to lock the document to.
|
|
660
|
+
heads?: UrlHeads
|
|
661
|
+
|
|
580
662
|
/** The number of milliseconds before we mark this document as unavailable if we don't have it and nobody shares it with us. */
|
|
581
663
|
timeoutDelay?: number
|
|
582
664
|
}
|
|
@@ -588,7 +670,6 @@ export interface DocHandleEvents<T> {
|
|
|
588
670
|
"heads-changed": (payload: DocHandleEncodedChangePayload<T>) => void
|
|
589
671
|
change: (payload: DocHandleChangePayload<T>) => void
|
|
590
672
|
delete: (payload: DocHandleDeletePayload<T>) => void
|
|
591
|
-
unavailable: (payload: DocHandleUnavailablePayload<T>) => void
|
|
592
673
|
"ephemeral-message": (payload: DocHandleEphemeralMessagePayload<T>) => void
|
|
593
674
|
"ephemeral-message-outbound": (
|
|
594
675
|
payload: DocHandleOutboundEphemeralMessagePayload<T>
|
|
@@ -640,7 +721,7 @@ export interface DocHandleOutboundEphemeralMessagePayload<T> {
|
|
|
640
721
|
/** Emitted when we have new remote heads for this document */
|
|
641
722
|
export interface DocHandleRemoteHeadsPayload {
|
|
642
723
|
storageId: StorageId
|
|
643
|
-
heads:
|
|
724
|
+
heads: UrlHeads
|
|
644
725
|
}
|
|
645
726
|
|
|
646
727
|
// STATE MACHINE TYPES & CONSTANTS
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DocHandle } from "./DocHandle.js"
|
|
2
|
+
|
|
3
|
+
export type FindProgressState =
|
|
4
|
+
| "loading"
|
|
5
|
+
| "ready"
|
|
6
|
+
| "failed"
|
|
7
|
+
| "aborted"
|
|
8
|
+
| "unavailable"
|
|
9
|
+
|
|
10
|
+
interface FindProgressBase<T> {
|
|
11
|
+
state: FindProgressState
|
|
12
|
+
handle: DocHandle<T>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FindProgressLoading<T> extends FindProgressBase<T> {
|
|
16
|
+
state: "loading"
|
|
17
|
+
progress: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FindProgressReady<T> extends FindProgressBase<T> {
|
|
21
|
+
state: "ready"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface FindProgressFailed<T> extends FindProgressBase<T> {
|
|
25
|
+
state: "failed"
|
|
26
|
+
error: Error
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface FindProgressUnavailable<T> extends FindProgressBase<T> {
|
|
30
|
+
state: "unavailable"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FindProgressAborted<T> extends FindProgressBase<T> {
|
|
34
|
+
state: "aborted"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type FindProgress<T> =
|
|
38
|
+
| FindProgressLoading<T>
|
|
39
|
+
| FindProgressReady<T>
|
|
40
|
+
| FindProgressFailed<T>
|
|
41
|
+
| FindProgressUnavailable<T>
|
|
42
|
+
| FindProgressAborted<T>
|
|
43
|
+
|
|
44
|
+
export type FindProgressWithMethods<T> = FindProgress<T> & {
|
|
45
|
+
next: () => Promise<FindProgressWithMethods<T>>
|
|
46
|
+
// TODO: i don't like this allowableStates
|
|
47
|
+
untilReady: (allowableStates: string[]) => Promise<DocHandle<T>>
|
|
48
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { next as A } from "@automerge/automerge/slim"
|
|
2
1
|
import { EventEmitter } from "eventemitter3"
|
|
3
|
-
import { DocumentId, PeerId } from "./types.js"
|
|
2
|
+
import { DocumentId, PeerId, UrlHeads } from "./types.js"
|
|
4
3
|
import {
|
|
5
4
|
RemoteHeadsChanged,
|
|
6
5
|
RemoteSubscriptionControlMessage,
|
|
@@ -12,7 +11,7 @@ import debug from "debug"
|
|
|
12
11
|
export type RemoteHeadsSubscriptionEventPayload = {
|
|
13
12
|
documentId: DocumentId
|
|
14
13
|
storageId: StorageId
|
|
15
|
-
remoteHeads:
|
|
14
|
+
remoteHeads: UrlHeads
|
|
16
15
|
timestamp: number
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -21,7 +20,7 @@ export type NotifyRemoteHeadsPayload = {
|
|
|
21
20
|
targetId: PeerId
|
|
22
21
|
documentId: DocumentId
|
|
23
22
|
storageId: StorageId
|
|
24
|
-
heads:
|
|
23
|
+
heads: UrlHeads
|
|
25
24
|
timestamp: number
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -216,7 +215,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
216
215
|
handleImmediateRemoteHeadsChanged(
|
|
217
216
|
documentId: DocumentId,
|
|
218
217
|
storageId: StorageId,
|
|
219
|
-
heads:
|
|
218
|
+
heads: UrlHeads
|
|
220
219
|
) {
|
|
221
220
|
this.#log("handleLocalHeadsChanged", documentId, storageId, heads)
|
|
222
221
|
const remote = this.#knownHeads.get(documentId)
|
|
@@ -334,7 +333,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
334
333
|
#changedHeads(msg: RemoteHeadsChanged): {
|
|
335
334
|
documentId: DocumentId
|
|
336
335
|
storageId: StorageId
|
|
337
|
-
remoteHeads:
|
|
336
|
+
remoteHeads: UrlHeads
|
|
338
337
|
timestamp: number
|
|
339
338
|
}[] {
|
|
340
339
|
const changedHeads = []
|
|
@@ -356,11 +355,14 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
356
355
|
if (docRemote && docRemote.timestamp >= timestamp) {
|
|
357
356
|
continue
|
|
358
357
|
} else {
|
|
359
|
-
remote.set(storageId as StorageId, {
|
|
358
|
+
remote.set(storageId as StorageId, {
|
|
359
|
+
timestamp,
|
|
360
|
+
heads: heads as UrlHeads,
|
|
361
|
+
})
|
|
360
362
|
changedHeads.push({
|
|
361
363
|
documentId,
|
|
362
364
|
storageId: storageId as StorageId,
|
|
363
|
-
remoteHeads: heads,
|
|
365
|
+
remoteHeads: heads as UrlHeads,
|
|
364
366
|
timestamp,
|
|
365
367
|
})
|
|
366
368
|
}
|
|
@@ -371,5 +373,5 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
371
373
|
|
|
372
374
|
type LastHeads = {
|
|
373
375
|
timestamp: number
|
|
374
|
-
heads:
|
|
376
|
+
heads: UrlHeads
|
|
375
377
|
}
|