@automerge/automerge-repo 1.0.1 → 1.0.3

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.
@@ -2,23 +2,44 @@ import { EventEmitter } from "eventemitter3"
2
2
  import { PeerId } from "../types.js"
3
3
  import { Message } from "./messages.js"
4
4
 
5
+ /** An interface representing some way to connect to other peers
6
+ *
7
+ * @remarks
8
+ * The {@link Repo} uses one or more `NetworkAdapter`s to connect to other peers.
9
+ * Because the network may take some time to be ready the {@link Repo} will wait
10
+ * until the adapter emits a `ready` event before it starts trying to use it
11
+ */
5
12
  export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
6
13
  peerId?: PeerId // hmmm, maybe not
7
14
 
15
+ /** Called by the {@link Repo} to start the connection process
16
+ *
17
+ * @argument peerId - the peerId of this repo
18
+ */
8
19
  abstract connect(peerId: PeerId): void
9
20
 
21
+ /** Called by the {@link Repo} to send a message to a peer
22
+ *
23
+ * @argument message - the message to send
24
+ */
10
25
  abstract send(message: Message): void
11
26
 
27
+ /** Called by the {@link Repo} to disconnect from the network */
12
28
  abstract disconnect(): void
13
29
  }
14
30
 
15
31
  // events & payloads
16
32
 
17
33
  export interface NetworkAdapterEvents {
34
+ /** Emitted when the network is ready to be used */
18
35
  ready: (payload: OpenPayload) => void
36
+ /** Emitted when the network is closed */
19
37
  close: () => void
38
+ /** Emitted when the network adapter learns about a new peer */
20
39
  "peer-candidate": (payload: PeerCandidatePayload) => void
40
+ /** Emitted when the network adapter learns that a peer has disconnected */
21
41
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void
42
+ /** Emitted when the network adapter receives a message from a peer */
22
43
  message: (payload: Message) => void
23
44
  }
24
45
 
@@ -1,5 +1,6 @@
1
1
  // utilities
2
2
  import { SessionId } from "../EphemeralData.js"
3
+ export { type SessionId } from "../EphemeralData.js"
3
4
  import { DocumentId, PeerId } from "../types.js"
4
5
 
5
6
  export function isValidMessage(
@@ -55,7 +56,28 @@ export interface SyncMessageContents {
55
56
  documentId: DocumentId
56
57
  }
57
58
 
58
- export type SyncMessage = SyncMessageEnvelope & SyncMessageContents
59
+ type static_assert<T extends true> = never
60
+
61
+ // export type SyncMessage = SyncMessageEnvelope & SyncMessageContents
62
+ // we inline the definitions here rather than using the above type alias because
63
+ // otherwise typedoc can't produce nice docs. We use this `static_assert` thing
64
+ // to make sure the types continue to line up though.
65
+ type _check_sync_message = static_assert<SyncMessage extends SyncMessageEnvelope & SyncMessageContents ? true : false>
66
+ /**
67
+ * A sync message for a particular document
68
+ */
69
+ export type SyncMessage = {
70
+ /** The peer ID of the sender of this message */
71
+ senderId: PeerId
72
+ type: "sync"
73
+ /** The automerge sync message */
74
+ data: Uint8Array
75
+ /** The peer ID of the recipient of this message */
76
+ targetId: PeerId
77
+ /** The document ID of the document this message is for */
78
+ documentId: DocumentId
79
+ }
80
+
59
81
 
60
82
  export interface EphemeralMessageEnvelope {
61
83
  senderId: PeerId
@@ -70,8 +92,35 @@ export interface EphemeralMessageContents {
70
92
  data: Uint8Array
71
93
  }
72
94
 
73
- export type EphemeralMessage = EphemeralMessageEnvelope &
74
- EphemeralMessageContents
95
+ // export type EphemeralMessage = EphemeralMessageEnvelope & EphemeralMessageContents
96
+ // Inline definitions to get good docs, but check the types line up
97
+ type _check_ephemeral_message = static_assert<EphemeralMessage extends EphemeralMessageEnvelope & EphemeralMessageContents ? true : false>
98
+
99
+ /** An ephemeral message
100
+ *
101
+ * @remarks
102
+ * Ephemeral messages are not persisted anywhere and have no particular
103
+ * structure. `automerge-repo` will gossip them around, in order to avoid
104
+ * eternal loops of ephemeral messages every message has a session ID, which
105
+ * is a random number generated by the sender at startup time, and a sequence
106
+ * number. The combination of these two things allows us to discard messages
107
+ * we have already seen.
108
+ * */
109
+ export type EphemeralMessage = {
110
+ /** The ID of the peer who sent this message */
111
+ senderId: PeerId
112
+ /** A sequence number which must be incremented for each message sent by this peer */
113
+ count: number
114
+ /** The ID of the session this message is part of. The sequence number for a given session always increases */
115
+ sessionId: SessionId
116
+ type: "ephemeral"
117
+ /** The peer this message is for */
118
+ targetId: PeerId
119
+ /** The document ID this message pertains to */
120
+ documentId: DocumentId
121
+ /** The actual data of the message */
122
+ data: Uint8Array
123
+ }
75
124
 
76
125
  export interface DocumentUnavailableMessageContents {
77
126
  type: "doc-unavailable"
@@ -79,8 +128,20 @@ export interface DocumentUnavailableMessageContents {
79
128
  targetId: PeerId
80
129
  }
81
130
 
82
- export type DocumentUnavailableMessage = SyncMessageEnvelope &
83
- DocumentUnavailableMessageContents
131
+
132
+ // export type DocumentUnavailableMessage = SyncMessageEnvelope & DocumentUnavailableMessageContents
133
+ // Inline definitions to get good docs, but check the types line up
134
+ type _check_doc_unavailable = static_assert<DocumentUnavailableMessage extends SyncMessageEnvelope & DocumentUnavailableMessageContents ? true : false>
135
+ /** Sent by a {@link Repo} to indicate that it does not have the document and none of it's connected peers do either */
136
+ export type DocumentUnavailableMessage = {
137
+ /** The peer who sent this message */
138
+ senderId: PeerId
139
+ type: "doc-unavailable"
140
+ /** The document which the peer claims it doesn't have */
141
+ documentId: DocumentId
142
+ /** The peer this message is for */
143
+ targetId: PeerId
144
+ }
84
145
 
85
146
  export interface RequestMessageContents {
86
147
  type: "request"
@@ -89,7 +150,29 @@ export interface RequestMessageContents {
89
150
  documentId: DocumentId
90
151
  }
91
152
 
92
- export type RequestMessage = SyncMessageEnvelope & RequestMessageContents
153
+ // export type RequestMessage = SyncMessageEnvelope & RequestMessageContents
154
+ // Inline definitions to get good docs, but check the types line up
155
+ type _check_request_message = static_assert<RequestMessage extends SyncMessageEnvelope & RequestMessageContents ? true : false>
156
+ // We inline the definitions here rather than using the above type alias because
157
+ // otherwise typedoc can't produce nice docs without exporting SyncMessageEnvelope
158
+ // and RequestMessageContents
159
+ /** Sent by a {@link Repo} to request a document from a peer
160
+ *
161
+ * @remarks
162
+ * This is identical to a {@link SyncMessage} except that it is sent by a {@link Repo}
163
+ * as the initial sync message when asking the other peer if it has the document.
164
+ * */
165
+ export type RequestMessage = {
166
+ /** The peer who sent this message */
167
+ senderId: PeerId
168
+ type: "request"
169
+ /** The initial automerge sync message */
170
+ data: Uint8Array
171
+ /** The peer this message is for */
172
+ targetId: PeerId
173
+ /** The document ID this message requests */
174
+ documentId: DocumentId
175
+ }
93
176
 
94
177
  export type MessageContents =
95
178
  | SyncMessageContents
@@ -97,6 +180,7 @@ export type MessageContents =
97
180
  | RequestMessageContents
98
181
  | DocumentUnavailableMessageContents
99
182
 
183
+ /** The type of messages that {@link Repo} sends and receive to {@link NetworkAdapter}s */
100
184
  export type Message =
101
185
  | SyncMessage
102
186
  | EphemeralMessage
@@ -109,15 +193,28 @@ export type SynchronizerMessage =
109
193
  | DocumentUnavailableMessage
110
194
  | EphemeralMessage
111
195
 
112
- type ArriveMessage = {
196
+
197
+ /** Notify the network that we have arrived so everyone knows our peer ID */
198
+ export type ArriveMessage = {
199
+ /** Our peer ID */
113
200
  senderId: PeerId
114
201
  type: "arrive"
115
202
  }
116
203
 
117
- type WelcomeMessage = {
204
+ /** Respond to an arriving peer with our peer ID */
205
+ export type WelcomeMessage = {
206
+ /** Our peer ID */
118
207
  senderId: PeerId
208
+ /** The ID of the peer who sent the {@link ArriveMessage} we are responding to */
119
209
  targetId: PeerId
120
210
  type: "welcome"
121
211
  }
122
212
 
213
+ /** The type of messages that {@link NetworkAdapter}s send and receive to each other
214
+ *
215
+ * @remarks
216
+ * It is not _required_ that a {@link NetworkAdapter} use this message type.
217
+ * NetworkAdapters are free to use whatever message type makes sense for their
218
+ * transport. However, this type is a useful default.
219
+ * */
123
220
  export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message
@@ -1,11 +1,20 @@
1
+ /** A storage adapter represents some way of storing binary data for a {@link Repo}
2
+ *
3
+ * @remarks
4
+ * `StorageAdapter`s are a little like a key/value store. The keys are arrays
5
+ * of strings ({@link StorageKey}) and the values are binary blobs.
6
+ */
1
7
  export abstract class StorageAdapter {
2
8
  // load, store, or remove a single binary blob based on an array key
3
9
  // automerge-repo mostly uses keys in the following form:
4
10
  // [documentId, "snapshot"] or [documentId, "incremental", "0"]
5
11
  // but the storage adapter is agnostic to the meaning of the key
6
12
  // and we expect to store other data in the future such as syncstates
13
+ /** Load the single blob correspongind to `key` */
7
14
  abstract load(key: StorageKey): Promise<Uint8Array | undefined>
15
+ /** save the blod `data` to the key `key` */
8
16
  abstract save(key: StorageKey, data: Uint8Array): Promise<void>
17
+ /** remove the blob corresponding to `key` */
9
18
  abstract remove(key: StorageKey): Promise<void>
10
19
 
11
20
  // the keyprefix will match any key that starts with the given array
@@ -13,8 +22,20 @@ export abstract class StorageAdapter {
13
22
  // or [documentId] will match all data for a given document
14
23
  // be careful! this will also match [documentId, "syncState"]!
15
24
  // (we aren't using this yet but keep it in mind.)
25
+ /** Load all blobs with keys that start with `keyPrefix` */
16
26
  abstract loadRange(keyPrefix: StorageKey): Promise<{key: StorageKey, data: Uint8Array}[]>
27
+ /** Remove all blobs with keys that start with `keyPrefix` */
17
28
  abstract removeRange(keyPrefix: StorageKey): Promise<void>
18
29
  }
19
30
 
31
+ /** The type of keys for a {@link StorageAdapter}
32
+ *
33
+ * @remarks
34
+ * Storage keys are arrays because they are hierarchical and the storage
35
+ * subsystem will need to be able to do range queries for all keys that
36
+ * have a particular prefix. For example, incremental changes for a given
37
+ * document might be stored under `[<documentId>, "incremental", <SHA256>]`.
38
+ * `StorageAdapter` implementations should not assume any particular structure
39
+ * though.
40
+ **/
20
41
  export type StorageKey = string[]
package/src/types.ts CHANGED
@@ -1,9 +1,17 @@
1
+ /** The ID of a document. Typically you should use a {@link AutomergeUrl} instead.
2
+ */
1
3
  export type DocumentId = string & { __documentId: true } // for logging
4
+ /** A branded string representing a URL for a document
5
+ *
6
+ * @remarks
7
+ * An automerge URL has the form `automerge:<base58 encoded string>`. This
8
+ * type is returned from various routines which validate a url.
9
+ *
10
+ */
2
11
  export type AutomergeUrl = string & { __documentUrl: true } // for opening / linking
12
+ /** A document ID as a Uint8Array instead of a bas58 encoded string. Typically you should use a {@link AutomergeUrl} instead.
13
+ */
3
14
  export type BinaryDocumentId = Uint8Array & { __binaryDocumentId: true } // for storing / syncing
4
15
 
16
+ /** A branded type for peer IDs */
5
17
  export type PeerId = string & { __peerId: false }
6
-
7
- export type DistributiveOmit<T, K extends keyof any> = T extends any
8
- ? Omit<T, K>
9
- : never
package/test/Repo.test.ts CHANGED
@@ -22,6 +22,9 @@ import {
22
22
  generateLargeObject,
23
23
  LargeObject,
24
24
  } from "./helpers/generate-large-object.js"
25
+ import { parseAutomergeUrl } from "../dist/DocUrl.js"
26
+
27
+ import * as Uuid from "uuid"
25
28
 
26
29
  describe("Repo", () => {
27
30
  describe("single repo", () => {
@@ -50,6 +53,34 @@ describe("Repo", () => {
50
53
  assert.equal(handle.isReady(), true)
51
54
  })
52
55
 
56
+ it("can find a document once it's created", () => {
57
+ const { repo } = setup()
58
+ const handle = repo.create<TestDoc>()
59
+ handle.change((d: TestDoc) => {
60
+ d.foo = "bar"
61
+ })
62
+
63
+ const handle2 = repo.find(handle.url)
64
+ assert.equal(handle, handle2)
65
+ assert.deepEqual(handle2.docSync(), { foo: "bar" })
66
+ })
67
+
68
+ it("can find a document using a legacy UUID (for now)", () => {
69
+ const { repo } = setup()
70
+ const handle = repo.create<TestDoc>()
71
+ handle.change((d: TestDoc) => {
72
+ d.foo = "bar"
73
+ })
74
+
75
+ const url = handle.url
76
+ const { binaryDocumentId } = parseAutomergeUrl(url)
77
+ const legacyDocumentId = Uuid.stringify(binaryDocumentId) as AutomergeUrl // a white lie
78
+
79
+ const handle2 = repo.find(legacyDocumentId)
80
+ assert.equal(handle, handle2)
81
+ assert.deepEqual(handle2.docSync(), { foo: "bar" })
82
+ })
83
+
53
84
  it("can change a document", async () => {
54
85
  const { repo } = setup()
55
86
  const handle = repo.create<TestDoc>()
@@ -9,7 +9,7 @@ import * as A from "@automerge/automerge/next"
9
9
  import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
10
10
  import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs"
11
11
 
12
- import { StorageSubsystem } from "../src/index.js"
12
+ import { StorageSubsystem } from "../src/storage/StorageSubsystem.js"
13
13
  import { generateAutomergeUrl, parseAutomergeUrl } from "../src/DocUrl.js"
14
14
 
15
15
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "automerge-repo-tests"))
package/typedoc.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../typedoc.base.json",
3
+ "entryPoints": ["src/index.ts"],
4
+ "readme": "none"
5
+ }