@automerge/automerge-repo 2.0.0-collectionsync-alpha.1 → 2.0.1

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
package/dist/ferigan.js DELETED
@@ -1,98 +0,0 @@
1
- import { EventEmitter } from "eventemitter3";
2
- const URL_RE = /automerge:([\w\d+/=]+)(?:\?([\w\d+/=&]*))*/;
3
- export function makeFerigan(repo) {
4
- function fakeProgress() {
5
- return (async function* () {
6
- yield { type: "synchronizing_index" };
7
- })();
8
- }
9
- class FakeFerigan extends EventEmitter {
10
- #repo;
11
- constructor(repo) {
12
- super();
13
- this.#repo = repo;
14
- }
15
- async receiveMessage(message) { }
16
- load(doc) {
17
- return fakeProgress();
18
- }
19
- async *loadCollection(doc) {
20
- yield { type: "synchronizing_index" };
21
- const index = { rootUrl: doc, entries: [] };
22
- function findLinks(obj) {
23
- const links = [];
24
- const traverse = (value) => {
25
- if (typeof value === "string") {
26
- const url = parseUrl(value);
27
- if (url != null) {
28
- links.push(url);
29
- }
30
- }
31
- else if (Array.isArray(value)) {
32
- value.forEach(traverse);
33
- }
34
- else if (typeof value === "object" && value !== null) {
35
- Object.values(value).forEach(traverse);
36
- }
37
- };
38
- traverse(obj);
39
- return links;
40
- }
41
- const indexDoc = this.#repo.find(doc);
42
- const handlesToProcess = [indexDoc];
43
- while (handlesToProcess.length > 0) {
44
- const handle = handlesToProcess.pop();
45
- if (!handle) {
46
- continue;
47
- }
48
- const doc = await handle.doc();
49
- if (!doc) {
50
- continue;
51
- }
52
- handle.on("change", change => {
53
- for (const patch of change.patches) {
54
- if (patch.action === "splice") {
55
- const possibleUrl = parseUrl(patch.value);
56
- if (possibleUrl != null) {
57
- this.emit("indexChanged", {
58
- indexUrl: indexDoc.url,
59
- change: {
60
- type: "add",
61
- url: possibleUrl.url,
62
- }
63
- });
64
- }
65
- }
66
- }
67
- });
68
- const links = findLinks(doc);
69
- for (const { url: urlStr } of links) {
70
- const url = urlStr;
71
- if (index.entries.some(entry => entry === url)) {
72
- continue;
73
- }
74
- index.entries.push(url);
75
- const childHandle = this.#repo.find(url);
76
- handlesToProcess.push(childHandle);
77
- }
78
- }
79
- yield { type: "done", value: index };
80
- }
81
- append(doc, parents, changes) {
82
- return Promise.resolve();
83
- }
84
- replace(doc, start, end, changes) {
85
- return Promise.resolve();
86
- }
87
- }
88
- return new FakeFerigan(repo);
89
- }
90
- function parseUrl(urlStr) {
91
- const match = urlStr.match(URL_RE);
92
- if (!match) {
93
- return undefined;
94
- }
95
- const url = new URL(match[0]);
96
- const normalisedUrl = `automerge:${url.pathname}`;
97
- return { url: normalisedUrl };
98
- }
@@ -1,182 +0,0 @@
1
- import * as A from "@automerge/automerge/next"
2
- import { EventEmitter } from "eventemitter3"
3
- import { StateValue } from "xstate"
4
- import type { DocumentId, PeerId, AutomergeUrl } from "./types.js"
5
- /** DocHandle is a wrapper around a single Automerge document that lets us
6
- * listen for changes and notify the network and storage of new changes.
7
- *
8
- * @remarks
9
- * A `DocHandle` represents a document which is being managed by a {@link Repo}.
10
- * To obtain `DocHandle` use {@link Repo.find} or {@link Repo.create}.
11
- *
12
- * To modify the underlying document use either {@link DocHandle.change} or
13
- * {@link DocHandle.changeAt}. These methods will notify the `Repo` that some
14
- * change has occured and the `Repo` will save any new changes to the
15
- * attached {@link StorageAdapter} and send sync messages to connected peers.
16
- * */
17
- export declare class DocHandle<T> //
18
- extends EventEmitter<DocHandleEvents<T>>
19
- {
20
- #private
21
- documentId: DocumentId
22
- /** The URL of this document
23
- *
24
- * @remarks
25
- * This can be used to request the document from an instance of {@link Repo}
26
- */
27
- get url(): AutomergeUrl
28
- /** @hidden */
29
- constructor(
30
- documentId: DocumentId,
31
- { isNew, timeoutDelay }?: DocHandleOptions
32
- )
33
- /**
34
- * Checks if the document is ready for accessing or changes.
35
- * Note that for documents already stored locally this occurs before synchronization
36
- * with any peers. We do not currently have an equivalent `whenSynced()`.
37
- */
38
- isReady: () => boolean
39
- /**
40
- * Checks if this document has been marked as deleted.
41
- * Deleted documents are removed from local storage and the sync process.
42
- * It's not currently possible at runtime to undelete a document.
43
- * @returns true if the document has been marked as deleted
44
- */
45
- isDeleted: () => boolean
46
- isUnavailable: () => boolean
47
- inState: (states: HandleState[]) => boolean
48
- /** @hidden */
49
- get state(): StateValue
50
- /**
51
- * Use this to block until the document handle has finished loading.
52
- * The async equivalent to checking `inState()`.
53
- * @param awaitStates = [READY]
54
- * @returns
55
- */
56
- whenReady(awaitStates?: HandleState[]): Promise<void>
57
- /**
58
- * Returns the current state of the Automerge document this handle manages.
59
- * Note that this waits for the handle to be ready if necessary, and currently, if
60
- * loading (or synchronization) fails, will never resolve.
61
- *
62
- * @param {awaitStates=[READY]} optional states to wait for, such as "LOADING". mostly for internal use.
63
- */
64
- doc(awaitStates?: HandleState[]): Promise<A.Doc<T> | undefined>
65
- /**
66
- * Returns the current state of the Automerge document this handle manages, or undefined.
67
- * Useful in a synchronous context. Consider using `await handle.doc()` instead, check `isReady()`,
68
- * or use `whenReady()` if you want to make sure loading is complete first.
69
- *
70
- * Do not confuse this with the SyncState of the document, which describes the state of the synchronization process.
71
- *
72
- * Note that `undefined` is not a valid Automerge document so the return from this function is unambigous.
73
- * @returns the current document, or undefined if the document is not ready
74
- */
75
- docSync(): A.Doc<T> | undefined
76
- /** `update` is called by the repo when we receive changes from the network
77
- * @hidden
78
- * */
79
- update(callback: (doc: A.Doc<T>) => A.Doc<T>): void
80
- /** `change` is called by the repo when the document is changed locally */
81
- change(callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): void
82
- /** Make a change as if the document were at `heads`
83
- *
84
- * @returns A set of heads representing the concurrent change that was made.
85
- */
86
- changeAt(
87
- heads: A.Heads,
88
- callback: A.ChangeFn<T>,
89
- options?: A.ChangeOptions<T>
90
- ): string[] | undefined
91
- unavailable(): void
92
- /** `request` is called by the repo when the document is not found in storage
93
- * @hidden
94
- * */
95
- request(): void
96
- /** @hidden */
97
- awaitNetwork(): void
98
- /** @hidden */
99
- networkReady(): void
100
- /** `delete` is called by the repo when the document is deleted */
101
- delete(): void
102
- /** `broadcast` sends an arbitrary ephemeral message out to all reachable peers who would receive sync messages from you
103
- * it has no guarantee of delivery, and is not persisted to the underlying automerge doc in any way.
104
- * messages will have a sending PeerId but this is *not* a useful user identifier.
105
- * a user could have multiple tabs open and would appear as multiple PeerIds.
106
- * every message source must have a unique PeerId.
107
- */
108
- broadcast(message: any): void
109
- }
110
- export interface DocHandleOptions {
111
- isNew?: boolean
112
- timeoutDelay?: number
113
- }
114
- export interface DocHandleMessagePayload {
115
- destinationId: PeerId
116
- documentId: DocumentId
117
- data: Uint8Array
118
- }
119
- export interface DocHandleEncodedChangePayload<T> {
120
- handle: DocHandle<T>
121
- doc: A.Doc<T>
122
- }
123
- export interface DocHandleDeletePayload<T> {
124
- handle: DocHandle<T>
125
- }
126
- export interface DocHandleChangePayload<T> {
127
- handle: DocHandle<T>
128
- doc: A.Doc<T>
129
- patches: A.Patch[]
130
- patchInfo: A.PatchInfo<T>
131
- }
132
- export interface DocHandleEphemeralMessagePayload {
133
- handle: DocHandle<any>
134
- senderId: PeerId
135
- message: unknown
136
- }
137
- export interface DocHandleOutboundEphemeralMessagePayload {
138
- handle: DocHandle<any>
139
- data: Uint8Array
140
- }
141
- export interface DocHandleEvents<T> {
142
- "heads-changed": (payload: DocHandleEncodedChangePayload<T>) => void
143
- change: (payload: DocHandleChangePayload<T>) => void
144
- delete: (payload: DocHandleDeletePayload<T>) => void
145
- unavailable: (payload: DocHandleDeletePayload<T>) => void
146
- "ephemeral-message": (payload: DocHandleEphemeralMessagePayload) => void
147
- "ephemeral-message-outbound": (
148
- payload: DocHandleOutboundEphemeralMessagePayload
149
- ) => void
150
- }
151
- export declare const HandleState: {
152
- readonly IDLE: "idle"
153
- readonly LOADING: "loading"
154
- readonly AWAITING_NETWORK: "awaitingNetwork"
155
- readonly REQUESTING: "requesting"
156
- readonly READY: "ready"
157
- readonly FAILED: "failed"
158
- readonly DELETED: "deleted"
159
- readonly UNAVAILABLE: "unavailable"
160
- }
161
- export type HandleState = (typeof HandleState)[keyof typeof HandleState]
162
- export declare const Event: {
163
- readonly CREATE: "CREATE"
164
- readonly FIND: "FIND"
165
- readonly REQUEST: "REQUEST"
166
- readonly REQUEST_COMPLETE: "REQUEST_COMPLETE"
167
- readonly AWAIT_NETWORK: "AWAIT_NETWORK"
168
- readonly NETWORK_READY: "NETWORK_READY"
169
- readonly UPDATE: "UPDATE"
170
- readonly TIMEOUT: "TIMEOUT"
171
- readonly DELETE: "DELETE"
172
- readonly MARK_UNAVAILABLE: "MARK_UNAVAILABLE"
173
- }
174
- export declare const IDLE: "idle",
175
- LOADING: "loading",
176
- AWAITING_NETWORK: "awaitingNetwork",
177
- REQUESTING: "requesting",
178
- READY: "ready",
179
- FAILED: "failed",
180
- DELETED: "deleted",
181
- UNAVAILABLE: "unavailable"
182
- //# sourceMappingURL=DocHandle.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../../src/DocHandle.ts"],"names":[],"mappings":"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"}
@@ -1,405 +0,0 @@
1
- import * as A from "@automerge/automerge/next"
2
- import debug from "debug"
3
- import { EventEmitter } from "eventemitter3"
4
- import { assign, createMachine, interpret } from "xstate"
5
- import { waitFor } from "xstate/lib/waitFor.js"
6
- import { headsAreSame } from "./helpers/headsAreSame.js"
7
- import { pause } from "./helpers/pause.js"
8
- import { TimeoutError, withTimeout } from "./helpers/withTimeout.js"
9
- import { stringifyAutomergeUrl } from "./DocUrl.js"
10
- import { encode } from "./helpers/cbor.js"
11
- /** DocHandle is a wrapper around a single Automerge document that lets us
12
- * listen for changes and notify the network and storage of new changes.
13
- *
14
- * @remarks
15
- * A `DocHandle` represents a document which is being managed by a {@link Repo}.
16
- * To obtain `DocHandle` use {@link Repo.find} or {@link Repo.create}.
17
- *
18
- * To modify the underlying document use either {@link DocHandle.change} or
19
- * {@link DocHandle.changeAt}. These methods will notify the `Repo` that some
20
- * change has occured and the `Repo` will save any new changes to the
21
- * attached {@link StorageAdapter} and send sync messages to connected peers.
22
- * */
23
- export class DocHandle //
24
- extends EventEmitter
25
- {
26
- documentId
27
- #log
28
- #machine
29
- #timeoutDelay
30
- /** The URL of this document
31
- *
32
- * @remarks
33
- * This can be used to request the document from an instance of {@link Repo}
34
- */
35
- get url() {
36
- return stringifyAutomergeUrl({ documentId: this.documentId })
37
- }
38
- /** @hidden */
39
- constructor(documentId, { isNew = false, timeoutDelay = 60_000 } = {}) {
40
- super()
41
- this.documentId = documentId
42
- this.#timeoutDelay = timeoutDelay
43
- this.#log = debug(`automerge-repo:dochandle:${this.documentId.slice(0, 5)}`)
44
- // initial doc
45
- let doc = A.init()
46
- // Make an empty change so that we have something to save to disk
47
- if (isNew) {
48
- doc = A.emptyChange(doc, {})
49
- }
50
- /**
51
- * Internally we use a state machine to orchestrate document loading and/or syncing, in order to
52
- * avoid requesting data we already have, or surfacing intermediate values to the consumer.
53
- *
54
- * ┌─────────────────────┬─────────TIMEOUT────►┌────────┐
55
- * ┌───┴─────┐ ┌───┴────────┐ │ failed │
56
- * ┌───────┐ ┌──FIND──┤ loading ├─REQUEST──►│ requesting ├─UPDATE──┐ └────────┘
57
- * │ idle ├──┤ └───┬─────┘ └────────────┘ │
58
- * └───────┘ │ │ └─►┌────────┐
59
- * │ └───────LOAD───────────────────────────────►│ ready │
60
- * └──CREATE───────────────────────────────────────────────►└────────┘
61
- */
62
- this.#machine = interpret(
63
- createMachine(
64
- {
65
- predictableActionArguments: true,
66
- id: "docHandle",
67
- initial: IDLE,
68
- context: { documentId: this.documentId, doc },
69
- states: {
70
- idle: {
71
- on: {
72
- // If we're creating a new document, we don't need to load anything
73
- CREATE: { target: READY },
74
- // If we're accessing an existing document, we need to request it from storage
75
- // and/or the network
76
- FIND: { target: LOADING },
77
- DELETE: { actions: "onDelete", target: DELETED },
78
- },
79
- },
80
- loading: {
81
- on: {
82
- // UPDATE is called by the Repo if the document is found in storage
83
- UPDATE: { actions: "onUpdate", target: READY },
84
- // REQUEST is called by the Repo if the document is not found in storage
85
- REQUEST: { target: REQUESTING },
86
- // AWAIT_NETWORK is called by the repo if the document is not found in storage but the network is not yet ready
87
- AWAIT_NETWORK: { target: AWAITING_NETWORK },
88
- DELETE: { actions: "onDelete", target: DELETED },
89
- },
90
- after: [
91
- {
92
- delay: this.#timeoutDelay,
93
- target: FAILED,
94
- },
95
- ],
96
- },
97
- awaitingNetwork: {
98
- on: {
99
- NETWORK_READY: { target: REQUESTING },
100
- },
101
- },
102
- requesting: {
103
- on: {
104
- MARK_UNAVAILABLE: {
105
- target: UNAVAILABLE,
106
- actions: "onUnavailable",
107
- },
108
- // UPDATE is called by the Repo when we receive changes from the network
109
- UPDATE: { actions: "onUpdate" },
110
- // REQUEST_COMPLETE is called from `onUpdate` when the doc has been fully loaded from the network
111
- REQUEST_COMPLETE: { target: READY },
112
- DELETE: { actions: "onDelete", target: DELETED },
113
- },
114
- after: [
115
- {
116
- delay: this.#timeoutDelay,
117
- target: FAILED,
118
- },
119
- ],
120
- },
121
- ready: {
122
- on: {
123
- // UPDATE is called by the Repo when we receive changes from the network
124
- UPDATE: { actions: "onUpdate", target: READY },
125
- DELETE: { actions: "onDelete", target: DELETED },
126
- },
127
- },
128
- failed: {
129
- type: "final",
130
- },
131
- deleted: {
132
- type: "final",
133
- },
134
- unavailable: {
135
- on: {
136
- UPDATE: { actions: "onUpdate" },
137
- // REQUEST_COMPLETE is called from `onUpdate` when the doc has been fully loaded from the network
138
- REQUEST_COMPLETE: { target: READY },
139
- DELETE: { actions: "onDelete", target: DELETED },
140
- },
141
- },
142
- },
143
- },
144
- {
145
- actions: {
146
- /** Put the updated doc on context */
147
- onUpdate: assign((context, { payload }) => {
148
- const { doc: oldDoc } = context
149
- const { callback } = payload
150
- const newDoc = callback(oldDoc)
151
- return { doc: newDoc }
152
- }),
153
- onDelete: assign(() => {
154
- this.emit("delete", { handle: this })
155
- return { doc: undefined }
156
- }),
157
- onUnavailable: assign(context => {
158
- const { doc } = context
159
- this.emit("unavailable", { handle: this })
160
- return { doc }
161
- }),
162
- },
163
- }
164
- )
165
- )
166
- .onTransition(({ value: state, history, context }, event) => {
167
- const oldDoc = history?.context?.doc
168
- const newDoc = context.doc
169
- this.#log(`${history?.value}: ${event.type} → ${state}`, newDoc)
170
- const docChanged =
171
- newDoc &&
172
- oldDoc &&
173
- !headsAreSame(A.getHeads(newDoc), A.getHeads(oldDoc))
174
- if (docChanged) {
175
- this.emit("heads-changed", { handle: this, doc: newDoc })
176
- const patches = A.diff(newDoc, A.getHeads(oldDoc), A.getHeads(newDoc))
177
- if (patches.length > 0) {
178
- const source = "change" // TODO: pass along the source (load/change/network)
179
- this.emit("change", {
180
- handle: this,
181
- doc: newDoc,
182
- patches,
183
- patchInfo: { before: oldDoc, after: newDoc, source },
184
- })
185
- }
186
- if (!this.isReady()) {
187
- this.#machine.send(REQUEST_COMPLETE)
188
- }
189
- }
190
- })
191
- .start()
192
- this.#machine.send(isNew ? CREATE : FIND)
193
- }
194
- // PRIVATE
195
- /** Returns the current document, regardless of state */
196
- get #doc() {
197
- return this.#machine?.getSnapshot().context.doc
198
- }
199
- /** Returns the docHandle's state (READY, etc.) */
200
- get #state() {
201
- return this.#machine?.getSnapshot().value
202
- }
203
- /** Returns a promise that resolves when the docHandle is in one of the given states */
204
- #statePromise(awaitStates) {
205
- if (!Array.isArray(awaitStates)) awaitStates = [awaitStates]
206
- return Promise.any(
207
- awaitStates.map(state =>
208
- waitFor(this.#machine, s => s.matches(state), {
209
- timeout: this.#timeoutDelay * 2000, // longer than the delay above for testing
210
- })
211
- )
212
- )
213
- }
214
- // PUBLIC
215
- /**
216
- * Checks if the document is ready for accessing or changes.
217
- * Note that for documents already stored locally this occurs before synchronization
218
- * with any peers. We do not currently have an equivalent `whenSynced()`.
219
- */
220
- isReady = () => this.inState([HandleState.READY])
221
- /**
222
- * Checks if this document has been marked as deleted.
223
- * Deleted documents are removed from local storage and the sync process.
224
- * It's not currently possible at runtime to undelete a document.
225
- * @returns true if the document has been marked as deleted
226
- */
227
- isDeleted = () => this.inState([HandleState.DELETED])
228
- isUnavailable = () => this.inState([HandleState.UNAVAILABLE])
229
- inState = states => states.some(this.#machine?.getSnapshot().matches)
230
- /** @hidden */
231
- get state() {
232
- return this.#machine?.getSnapshot().value
233
- }
234
- /**
235
- * Use this to block until the document handle has finished loading.
236
- * The async equivalent to checking `inState()`.
237
- * @param awaitStates = [READY]
238
- * @returns
239
- */
240
- async whenReady(awaitStates = [READY]) {
241
- await withTimeout(this.#statePromise(awaitStates), this.#timeoutDelay)
242
- }
243
- /**
244
- * Returns the current state of the Automerge document this handle manages.
245
- * Note that this waits for the handle to be ready if necessary, and currently, if
246
- * loading (or synchronization) fails, will never resolve.
247
- *
248
- * @param {awaitStates=[READY]} optional states to wait for, such as "LOADING". mostly for internal use.
249
- */
250
- async doc(awaitStates = [READY, UNAVAILABLE]) {
251
- await pause() // yield one tick because reasons
252
- try {
253
- // wait for the document to enter one of the desired states
254
- await this.#statePromise(awaitStates)
255
- } catch (error) {
256
- if (error instanceof TimeoutError)
257
- throw new Error(`DocHandle: timed out loading ${this.documentId}`)
258
- else throw error
259
- }
260
- // Return the document
261
- return !this.isUnavailable() ? this.#doc : undefined
262
- }
263
- /**
264
- * Returns the current state of the Automerge document this handle manages, or undefined.
265
- * Useful in a synchronous context. Consider using `await handle.doc()` instead, check `isReady()`,
266
- * or use `whenReady()` if you want to make sure loading is complete first.
267
- *
268
- * Do not confuse this with the SyncState of the document, which describes the state of the synchronization process.
269
- *
270
- * Note that `undefined` is not a valid Automerge document so the return from this function is unambigous.
271
- * @returns the current document, or undefined if the document is not ready
272
- */
273
- docSync() {
274
- if (!this.isReady()) {
275
- return undefined
276
- }
277
- return this.#doc
278
- }
279
- /** `update` is called by the repo when we receive changes from the network
280
- * @hidden
281
- * */
282
- update(callback) {
283
- this.#machine.send(UPDATE, {
284
- payload: { callback },
285
- })
286
- }
287
- /** `change` is called by the repo when the document is changed locally */
288
- change(callback, options = {}) {
289
- if (!this.isReady()) {
290
- throw new Error(
291
- `DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before accessing the document.`
292
- )
293
- }
294
- this.#machine.send(UPDATE, {
295
- payload: {
296
- callback: doc => {
297
- return A.change(doc, options, callback)
298
- },
299
- },
300
- })
301
- }
302
- /** Make a change as if the document were at `heads`
303
- *
304
- * @returns A set of heads representing the concurrent change that was made.
305
- */
306
- changeAt(heads, callback, options = {}) {
307
- if (!this.isReady()) {
308
- throw new Error(
309
- `DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before accessing the document.`
310
- )
311
- }
312
- let resultHeads = undefined
313
- this.#machine.send(UPDATE, {
314
- payload: {
315
- callback: doc => {
316
- const result = A.changeAt(doc, heads, options, callback)
317
- resultHeads = result.newHeads
318
- return result.newDoc
319
- },
320
- },
321
- })
322
- return resultHeads
323
- }
324
- unavailable() {
325
- this.#machine.send(MARK_UNAVAILABLE)
326
- }
327
- /** `request` is called by the repo when the document is not found in storage
328
- * @hidden
329
- * */
330
- request() {
331
- if (this.#state === LOADING) this.#machine.send(REQUEST)
332
- }
333
- /** @hidden */
334
- awaitNetwork() {
335
- if (this.#state === LOADING) this.#machine.send(AWAIT_NETWORK)
336
- }
337
- /** @hidden */
338
- networkReady() {
339
- if (this.#state === AWAITING_NETWORK) this.#machine.send(NETWORK_READY)
340
- }
341
- /** `delete` is called by the repo when the document is deleted */
342
- delete() {
343
- this.#machine.send(DELETE)
344
- }
345
- /** `broadcast` sends an arbitrary ephemeral message out to all reachable peers who would receive sync messages from you
346
- * it has no guarantee of delivery, and is not persisted to the underlying automerge doc in any way.
347
- * messages will have a sending PeerId but this is *not* a useful user identifier.
348
- * a user could have multiple tabs open and would appear as multiple PeerIds.
349
- * every message source must have a unique PeerId.
350
- */
351
- broadcast(message) {
352
- this.emit("ephemeral-message-outbound", {
353
- handle: this,
354
- data: encode(message),
355
- })
356
- }
357
- }
358
- // STATE MACHINE TYPES
359
- // state
360
- export const HandleState = {
361
- IDLE: "idle",
362
- LOADING: "loading",
363
- AWAITING_NETWORK: "awaitingNetwork",
364
- REQUESTING: "requesting",
365
- READY: "ready",
366
- FAILED: "failed",
367
- DELETED: "deleted",
368
- UNAVAILABLE: "unavailable",
369
- }
370
- // events
371
- export const Event = {
372
- CREATE: "CREATE",
373
- FIND: "FIND",
374
- REQUEST: "REQUEST",
375
- REQUEST_COMPLETE: "REQUEST_COMPLETE",
376
- AWAIT_NETWORK: "AWAIT_NETWORK",
377
- NETWORK_READY: "NETWORK_READY",
378
- UPDATE: "UPDATE",
379
- TIMEOUT: "TIMEOUT",
380
- DELETE: "DELETE",
381
- MARK_UNAVAILABLE: "MARK_UNAVAILABLE",
382
- }
383
- // CONSTANTS
384
- export const {
385
- IDLE,
386
- LOADING,
387
- AWAITING_NETWORK,
388
- REQUESTING,
389
- READY,
390
- FAILED,
391
- DELETED,
392
- UNAVAILABLE,
393
- } = HandleState
394
- const {
395
- CREATE,
396
- FIND,
397
- REQUEST,
398
- UPDATE,
399
- TIMEOUT,
400
- DELETE,
401
- REQUEST_COMPLETE,
402
- MARK_UNAVAILABLE,
403
- AWAIT_NETWORK,
404
- NETWORK_READY,
405
- } = Event