@automerge/automerge-repo 1.0.5 → 1.0.7

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 (65) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/DocHandle.d.ts +20 -7
  3. package/dist/DocHandle.d.ts.map +1 -1
  4. package/dist/DocHandle.js +27 -7
  5. package/dist/EphemeralData.d.ts +2 -2
  6. package/dist/EphemeralData.d.ts.map +1 -1
  7. package/dist/Repo.d.ts +16 -0
  8. package/dist/Repo.d.ts.map +1 -1
  9. package/dist/Repo.js +38 -10
  10. package/dist/helpers/cbor.d.ts +2 -2
  11. package/dist/helpers/cbor.d.ts.map +1 -1
  12. package/dist/helpers/cbor.js +1 -1
  13. package/dist/helpers/pause.d.ts.map +1 -1
  14. package/dist/helpers/pause.js +3 -1
  15. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  16. package/dist/helpers/tests/network-adapter-tests.js +2 -2
  17. package/dist/index.d.ts +11 -9
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -4
  20. package/dist/network/NetworkAdapter.d.ts +3 -3
  21. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  22. package/dist/network/NetworkSubsystem.d.ts +2 -2
  23. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  24. package/dist/network/NetworkSubsystem.js +30 -18
  25. package/dist/network/messages.d.ts +38 -68
  26. package/dist/network/messages.d.ts.map +1 -1
  27. package/dist/network/messages.js +13 -21
  28. package/dist/storage/StorageSubsystem.js +7 -7
  29. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -3
  30. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  31. package/dist/synchronizer/CollectionSynchronizer.js +2 -2
  32. package/dist/synchronizer/DocSynchronizer.d.ts +3 -3
  33. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  34. package/dist/synchronizer/DocSynchronizer.js +22 -29
  35. package/dist/synchronizer/Synchronizer.d.ts +2 -2
  36. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  37. package/dist/types.d.ts +5 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +5 -13
  40. package/src/DocHandle.ts +38 -14
  41. package/src/EphemeralData.ts +2 -2
  42. package/src/Repo.ts +46 -12
  43. package/src/helpers/cbor.ts +4 -4
  44. package/src/helpers/pause.ts +7 -2
  45. package/src/helpers/tests/network-adapter-tests.ts +3 -3
  46. package/src/helpers/withTimeout.ts +2 -2
  47. package/src/index.ts +36 -29
  48. package/src/network/NetworkAdapter.ts +7 -3
  49. package/src/network/NetworkSubsystem.ts +31 -23
  50. package/src/network/messages.ts +88 -151
  51. package/src/storage/StorageSubsystem.ts +8 -8
  52. package/src/synchronizer/CollectionSynchronizer.ts +6 -15
  53. package/src/synchronizer/DocSynchronizer.ts +34 -48
  54. package/src/synchronizer/Synchronizer.ts +2 -2
  55. package/src/types.ts +8 -3
  56. package/test/CollectionSynchronizer.test.ts +58 -53
  57. package/test/DocHandle.test.ts +35 -36
  58. package/test/DocSynchronizer.test.ts +9 -8
  59. package/test/Network.test.ts +1 -0
  60. package/test/Repo.test.ts +273 -88
  61. package/test/StorageSubsystem.test.ts +6 -9
  62. package/test/tsconfig.json +8 -0
  63. package/test/types.ts +2 -0
  64. package/typedoc.json +3 -3
  65. package/.mocharc.json +0 -5
@@ -1,161 +1,70 @@
1
- // utilities
2
- import { SessionId } from "../EphemeralData.js"
3
- export { type SessionId } from "../EphemeralData.js"
4
- import { DocumentId, PeerId } from "../types.js"
5
-
6
- export function isValidMessage(
7
- message: NetworkAdapterMessage
8
- ): message is
9
- | SyncMessage
10
- | EphemeralMessage
11
- | RequestMessage
12
- | DocumentUnavailableMessage {
13
- return (
14
- typeof message === "object" &&
15
- typeof message.type === "string" &&
16
- typeof message.senderId === "string" &&
17
- (isSyncMessage(message) ||
18
- isEphemeralMessage(message) ||
19
- isRequestMessage(message) ||
20
- isDocumentUnavailableMessage(message))
21
- )
22
- }
23
-
24
- export function isDocumentUnavailableMessage(
25
- message: NetworkAdapterMessage
26
- ): message is DocumentUnavailableMessage {
27
- return message.type === "doc-unavailable"
28
- }
29
-
30
- export function isRequestMessage(
31
- message: NetworkAdapterMessage
32
- ): message is RequestMessage {
33
- return message.type === "request"
34
- }
35
-
36
- export function isSyncMessage(
37
- message: NetworkAdapterMessage
38
- ): message is SyncMessage {
39
- return message.type === "sync"
40
- }
41
-
42
- export function isEphemeralMessage(
43
- message: NetworkAdapterMessage | MessageContents
44
- ): message is EphemeralMessage | EphemeralMessageContents {
45
- return message.type === "ephemeral"
46
- }
47
-
48
- export interface SyncMessageEnvelope {
49
- senderId: PeerId
50
- }
1
+ import { DocumentId, PeerId, SessionId } from "../types.js"
51
2
 
52
- export interface SyncMessageContents {
53
- type: "sync"
54
- data: Uint8Array
55
- targetId: PeerId
56
- documentId: DocumentId
57
- }
58
-
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
3
  /**
67
4
  * A sync message for a particular document
68
5
  */
69
6
  export type SyncMessage = {
7
+ type: "sync"
8
+
70
9
  /** The peer ID of the sender of this message */
71
10
  senderId: PeerId
72
- type: "sync"
73
- /** The automerge sync message */
74
- data: Uint8Array
11
+
75
12
  /** The peer ID of the recipient of this message */
76
13
  targetId: PeerId
77
- /** The document ID of the document this message is for */
78
- documentId: DocumentId
79
- }
80
14
 
15
+ /** The automerge sync message */
16
+ data: Uint8Array
81
17
 
82
- export interface EphemeralMessageEnvelope {
83
- senderId: PeerId
84
- count: number
85
- sessionId: SessionId
86
- }
87
-
88
- export interface EphemeralMessageContents {
89
- type: "ephemeral"
90
- targetId: PeerId
18
+ /** The document ID of the document this message is for */
91
19
  documentId: DocumentId
92
- data: Uint8Array
93
20
  }
94
21
 
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
- *
22
+ /** An ephemeral message
23
+ *
101
24
  * @remarks
102
25
  * Ephemeral messages are not persisted anywhere and have no particular
103
- * structure. `automerge-repo` will gossip them around, in order to avoid
26
+ * structure. `automerge-repo` will gossip them around, in order to avoid
104
27
  * eternal loops of ephemeral messages every message has a session ID, which
105
28
  * is a random number generated by the sender at startup time, and a sequence
106
29
  * number. The combination of these two things allows us to discard messages
107
30
  * we have already seen.
108
31
  * */
109
32
  export type EphemeralMessage = {
110
- /** The ID of the peer who sent this message */
33
+ type: "ephemeral"
34
+
35
+ /** The peer ID of the sender of this message */
111
36
  senderId: PeerId
37
+
38
+ /** The peer ID of the recipient of this message */
39
+ targetId: PeerId
40
+
112
41
  /** A sequence number which must be incremented for each message sent by this peer */
113
42
  count: number
43
+
114
44
  /** The ID of the session this message is part of. The sequence number for a given session always increases */
115
45
  sessionId: SessionId
116
- type: "ephemeral"
117
- /** The peer this message is for */
118
- targetId: PeerId
46
+
119
47
  /** The document ID this message pertains to */
120
48
  documentId: DocumentId
49
+
121
50
  /** The actual data of the message */
122
51
  data: Uint8Array
123
52
  }
124
53
 
125
- export interface DocumentUnavailableMessageContents {
126
- type: "doc-unavailable"
127
- documentId: DocumentId
128
- targetId: PeerId
129
- }
130
-
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
54
  /** Sent by a {@link Repo} to indicate that it does not have the document and none of it's connected peers do either */
136
55
  export type DocumentUnavailableMessage = {
137
- /** The peer who sent this message */
138
- senderId: PeerId
139
56
  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
- }
145
57
 
146
- export interface RequestMessageContents {
147
- type: "request"
148
- data: Uint8Array
58
+ /** The peer ID of the sender of this message */
59
+ senderId: PeerId
60
+
61
+ /** The peer ID of the recipient of this message */
149
62
  targetId: PeerId
63
+
64
+ /** The document which the peer claims it doesn't have */
150
65
  documentId: DocumentId
151
66
  }
152
67
 
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
68
  /** Sent by a {@link Repo} to request a document from a peer
160
69
  *
161
70
  * @remarks
@@ -163,58 +72,86 @@ type _check_request_message = static_assert<RequestMessage extends SyncMessageEn
163
72
  * as the initial sync message when asking the other peer if it has the document.
164
73
  * */
165
74
  export type RequestMessage = {
166
- /** The peer who sent this message */
167
- senderId: PeerId
168
75
  type: "request"
76
+
77
+ /** The peer ID of the sender of this message */
78
+ senderId: PeerId
79
+
80
+ /** The peer ID of the recipient of this message */
81
+ targetId: PeerId
82
+
169
83
  /** The initial automerge sync message */
170
84
  data: Uint8Array
171
- /** The peer this message is for */
172
- targetId: PeerId
85
+
173
86
  /** The document ID this message requests */
174
87
  documentId: DocumentId
175
88
  }
176
89
 
177
- export type MessageContents =
178
- | SyncMessageContents
179
- | EphemeralMessageContents
180
- | RequestMessageContents
181
- | DocumentUnavailableMessageContents
182
-
183
- /** The type of messages that {@link Repo} sends and receive to {@link NetworkAdapter}s */
184
- export type Message =
185
- | SyncMessage
186
- | EphemeralMessage
187
- | RequestMessage
188
- | DocumentUnavailableMessage
189
-
190
- export type SynchronizerMessage =
191
- | SyncMessage
192
- | RequestMessage
193
- | DocumentUnavailableMessage
194
- | EphemeralMessage
195
-
196
-
197
90
  /** Notify the network that we have arrived so everyone knows our peer ID */
198
91
  export type ArriveMessage = {
199
- /** Our peer ID */
200
- senderId: PeerId
201
92
  type: "arrive"
93
+
94
+ /** The peer ID of the sender of this message */
95
+ senderId: PeerId
96
+
97
+ /** Arrive messages don't have a targetId */
98
+ targetId: never
202
99
  }
203
100
 
204
101
  /** Respond to an arriving peer with our peer ID */
205
102
  export type WelcomeMessage = {
206
- /** Our peer ID */
103
+ type: "welcome"
104
+
105
+ /** The peer ID of the recipient sender this message */
207
106
  senderId: PeerId
208
- /** The ID of the peer who sent the {@link ArriveMessage} we are responding to */
107
+
108
+ /** The peer ID of the recipient of this message */
209
109
  targetId: PeerId
210
- type: "welcome"
211
110
  }
212
111
 
213
- /** The type of messages that {@link NetworkAdapter}s send and receive to each other
112
+ /** These are message types that a {@link NetworkAdapter} surfaces to a {@link Repo}. */
113
+ export type RepoMessage =
114
+ | SyncMessage
115
+ | EphemeralMessage
116
+ | RequestMessage
117
+ | DocumentUnavailableMessage
118
+
119
+ /** These are all the message types that a {@link NetworkAdapter} might see.
214
120
  *
215
121
  * @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.
122
+ * It is not _required_ that a {@link NetworkAdapter} use these types: They are free to use
123
+ * whatever message type makes sense for their transport. However, this type is a useful default.
219
124
  * */
220
- export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message
125
+ export type Message = RepoMessage | ArriveMessage | WelcomeMessage
126
+
127
+ /**
128
+ * The contents of a message, without the sender ID or other properties added by the {@link NetworkSubsystem})
129
+ */
130
+ export type MessageContents<T extends Message = Message> =
131
+ T extends EphemeralMessage
132
+ ? Omit<T, "senderId" | "count" | "sessionId">
133
+ : Omit<T, "senderId">
134
+
135
+ // TYPE GUARDS
136
+
137
+ export const isValidRepoMessage = (message: Message): message is RepoMessage =>
138
+ typeof message === "object" &&
139
+ typeof message.type === "string" &&
140
+ typeof message.senderId === "string" &&
141
+ (isSyncMessage(message) ||
142
+ isEphemeralMessage(message) ||
143
+ isRequestMessage(message) ||
144
+ isDocumentUnavailableMessage(message))
145
+
146
+ // prettier-ignore
147
+ export const isDocumentUnavailableMessage = (msg: Message): msg is DocumentUnavailableMessage =>
148
+ msg.type === "doc-unavailable"
149
+
150
+ export const isRequestMessage = (msg: Message): msg is RequestMessage =>
151
+ msg.type === "request"
152
+
153
+ export const isSyncMessage = (msg: Message): msg is SyncMessage =>
154
+ msg.type === "sync"
155
+
156
+ export const isEphemeralMessage = (msg: Message): msg is EphemeralMessage =>
157
+ msg.type === "ephemeral"
@@ -24,8 +24,8 @@ function keyHash(binary: Uint8Array) {
24
24
  }
25
25
 
26
26
  function headsHash(heads: A.Heads): string {
27
- let encoder = new TextEncoder()
28
- let headsbinary = mergeArrays(heads.map((h: string) => encoder.encode(h)))
27
+ const encoder = new TextEncoder()
28
+ const headsbinary = mergeArrays(heads.map((h: string) => encoder.encode(h)))
29
29
  return keyHash(headsbinary)
30
30
  }
31
31
 
@@ -53,7 +53,7 @@ export class StorageSubsystem {
53
53
  if (!this.#chunkInfos.has(documentId)) {
54
54
  this.#chunkInfos.set(documentId, [])
55
55
  }
56
- this.#chunkInfos.get(documentId)!!.push({
56
+ this.#chunkInfos.get(documentId)!.push({
57
57
  key,
58
58
  type: "incremental",
59
59
  size: binary.length,
@@ -122,18 +122,18 @@ export class StorageSubsystem {
122
122
  if (!this.#shouldSave(documentId, doc)) {
123
123
  return
124
124
  }
125
- let sourceChunks = this.#chunkInfos.get(documentId) ?? []
125
+ const sourceChunks = this.#chunkInfos.get(documentId) ?? []
126
126
  if (this.#shouldCompact(sourceChunks)) {
127
- this.#saveTotal(documentId, doc, sourceChunks)
127
+ void this.#saveTotal(documentId, doc, sourceChunks)
128
128
  } else {
129
- this.#saveIncremental(documentId, doc)
129
+ void this.#saveIncremental(documentId, doc)
130
130
  }
131
131
  this.#storedHeads.set(documentId, A.getHeads(doc))
132
132
  }
133
133
 
134
134
  async remove(documentId: DocumentId) {
135
- this.#storageAdapter.removeRange([documentId, "snapshot"])
136
- this.#storageAdapter.removeRange([documentId, "incremental"])
135
+ void this.#storageAdapter.removeRange([documentId, "snapshot"])
136
+ void this.#storageAdapter.removeRange([documentId, "incremental"])
137
137
  }
138
138
 
139
139
  #shouldSave(documentId: DocumentId, doc: A.Doc<unknown>): boolean {
@@ -1,21 +1,12 @@
1
- import { Repo } from "../Repo.js"
2
1
  import { DocHandle } from "../DocHandle.js"
3
- import {
4
- documentIdToBinary,
5
- binaryToDocumentId,
6
- stringifyAutomergeUrl,
7
- } from "../DocUrl.js"
8
- import { PeerId, DocumentId } from "../types.js"
2
+ import { stringifyAutomergeUrl } from "../DocUrl.js"
3
+ import { Repo } from "../Repo.js"
4
+ import { DocumentId, PeerId } from "../types.js"
9
5
  import { DocSynchronizer } from "./DocSynchronizer.js"
10
6
  import { Synchronizer } from "./Synchronizer.js"
11
7
 
12
8
  import debug from "debug"
13
- import {
14
- DocumentUnavailableMessage,
15
- RequestMessage,
16
- SynchronizerMessage,
17
- SyncMessage,
18
- } from "../network/messages.js"
9
+ import { RepoMessage } from "../network/messages.js"
19
10
  const log = debug("automerge-repo:collectionsync")
20
11
 
21
12
  /** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
@@ -66,7 +57,7 @@ export class CollectionSynchronizer extends Synchronizer {
66
57
  * When we receive a sync message for a document we haven't got in memory, we
67
58
  * register it with the repo and start synchronizing
68
59
  */
69
- async receiveMessage(message: SynchronizerMessage) {
60
+ async receiveMessage(message: RepoMessage) {
70
61
  log(
71
62
  `onSyncMessage: ${message.senderId}, ${message.documentId}, ${
72
63
  "data" in message ? message.data.byteLength + "bytes" : ""
@@ -121,7 +112,7 @@ export class CollectionSynchronizer extends Synchronizer {
121
112
  this.#peers.add(peerId)
122
113
  for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
123
114
  const { documentId } = docSynchronizer
124
- this.repo.sharePolicy(peerId, documentId).then(okToShare => {
115
+ void this.repo.sharePolicy(peerId, documentId).then(okToShare => {
125
116
  if (okToShare) docSynchronizer.beginSync([peerId])
126
117
  })
127
118
  }
@@ -1,28 +1,26 @@
1
1
  import * as A from "@automerge/automerge/next"
2
+ import { decode } from "cbor-x"
3
+ import debug from "debug"
2
4
  import {
3
- AWAITING_NETWORK,
4
5
  DocHandle,
5
6
  DocHandleOutboundEphemeralMessagePayload,
6
7
  READY,
7
8
  REQUESTING,
8
9
  UNAVAILABLE,
9
10
  } from "../DocHandle.js"
10
- import { PeerId } from "../types.js"
11
- import { Synchronizer } from "./Synchronizer.js"
12
-
13
- import debug from "debug"
14
11
  import {
12
+ DocumentUnavailableMessage,
15
13
  EphemeralMessage,
16
- isDocumentUnavailableMessage,
17
- isRequestMessage,
18
- Message,
14
+ RepoMessage,
15
+ MessageContents,
19
16
  RequestMessage,
20
- SynchronizerMessage,
21
17
  SyncMessage,
18
+ isRequestMessage,
22
19
  } from "../network/messages.js"
20
+ import { PeerId } from "../types.js"
21
+ import { Synchronizer } from "./Synchronizer.js"
23
22
 
24
23
  type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants"
25
- import { decode } from "cbor-x"
26
24
 
27
25
  /**
28
26
  * DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
@@ -45,7 +43,7 @@ export class DocSynchronizer extends Synchronizer {
45
43
 
46
44
  #syncStarted = false
47
45
 
48
- constructor(private handle: DocHandle<any>) {
46
+ constructor(private handle: DocHandle<unknown>) {
49
47
  super()
50
48
  const docId = handle.documentId.slice(0, 5)
51
49
  this.#conciseLog = debug(`automerge-repo:concise:docsync:${docId}`) // Only logs one line per receive/send
@@ -82,7 +80,9 @@ export class DocSynchronizer extends Synchronizer {
82
80
  this.#peers.forEach(peerId => this.#sendSyncMessage(peerId, doc))
83
81
  }
84
82
 
85
- async #broadcastToPeers({ data }: DocHandleOutboundEphemeralMessagePayload) {
83
+ async #broadcastToPeers({
84
+ data,
85
+ }: DocHandleOutboundEphemeralMessagePayload<unknown>) {
86
86
  this.#log(`broadcastToPeers`, this.#peers)
87
87
  this.#peers.forEach(peerId => this.#sendEphemeralMessage(peerId, data))
88
88
  }
@@ -90,12 +90,13 @@ export class DocSynchronizer extends Synchronizer {
90
90
  #sendEphemeralMessage(peerId: PeerId, data: Uint8Array) {
91
91
  this.#log(`sendEphemeralMessage ->${peerId}`)
92
92
 
93
- this.emit("message", {
93
+ const message: MessageContents<EphemeralMessage> = {
94
94
  type: "ephemeral",
95
95
  targetId: peerId,
96
96
  documentId: this.handle.documentId,
97
97
  data,
98
- })
98
+ }
99
+ this.emit("message", message)
99
100
  }
100
101
 
101
102
  #getSyncState(peerId: PeerId) {
@@ -128,13 +129,11 @@ export class DocSynchronizer extends Synchronizer {
128
129
  const [newSyncState, message] = A.generateSyncMessage(doc, syncState)
129
130
  this.#setSyncState(peerId, newSyncState)
130
131
  if (message) {
131
- this.#logMessage(`sendSyncMessage 🡒 ${peerId}`, message)
132
-
133
- const decoded = A.decodeSyncMessage(message)
132
+ const isNew = A.getHeads(doc).length === 0
134
133
 
135
134
  if (
136
135
  !this.handle.isReady() &&
137
- decoded.heads.length === 0 &&
136
+ isNew &&
138
137
  newSyncState.sharedHeads.length === 0 &&
139
138
  !Object.values(this.#peerDocumentStatuses).includes("has") &&
140
139
  this.#peerDocumentStatuses[peerId] === "unknown"
@@ -145,43 +144,23 @@ export class DocSynchronizer extends Synchronizer {
145
144
  targetId: peerId,
146
145
  documentId: this.handle.documentId,
147
146
  data: message,
148
- })
147
+ } as RequestMessage)
149
148
  } else {
150
149
  this.emit("message", {
151
150
  type: "sync",
152
151
  targetId: peerId,
153
152
  data: message,
154
153
  documentId: this.handle.documentId,
155
- })
154
+ } as SyncMessage)
156
155
  }
157
156
 
158
157
  // if we have sent heads, then the peer now has or will have the document
159
- if (decoded.heads.length > 0) {
158
+ if (!isNew) {
160
159
  this.#peerDocumentStatuses[peerId] = "has"
161
160
  }
162
161
  }
163
162
  }
164
163
 
165
- #logMessage = (label: string, message: Uint8Array) => {
166
- // This is real expensive...
167
- return
168
-
169
- const size = message.byteLength
170
- const logText = `${label} ${size}b`
171
- const decoded = A.decodeSyncMessage(message)
172
-
173
- this.#conciseLog(logText)
174
- this.#log(logText, decoded)
175
-
176
- // expanding is expensive, so only do it if we're logging at this level
177
- const expanded = this.#opsLog.enabled
178
- ? decoded.changes.flatMap((change: A.Change) =>
179
- A.decodeChange(change).ops.map((op: any) => JSON.stringify(op))
180
- )
181
- : null
182
- this.#opsLog(logText, expanded)
183
- }
184
-
185
164
  /// PUBLIC
186
165
 
187
166
  hasPeer(peerId: PeerId) {
@@ -189,6 +168,7 @@ export class DocSynchronizer extends Synchronizer {
189
168
  }
190
169
 
191
170
  beginSync(peerIds: PeerId[]) {
171
+ const newPeers = new Set(peerIds.filter(peerId => !this.#peers.includes(peerId)))
192
172
  this.#log(`beginSync: ${peerIds.join(", ")}`)
193
173
 
194
174
  // HACK: if we have a sync state already, we round-trip it through the encoding system to make
@@ -204,15 +184,20 @@ export class DocSynchronizer extends Synchronizer {
204
184
  // At this point if we don't have anything in our storage, we need to use an empty doc to sync
205
185
  // with; but we don't want to surface that state to the front end
206
186
  void this.handle.doc([READY, REQUESTING, UNAVAILABLE]).then(doc => {
207
-
208
187
  // we register out peers first, then say that sync has started
209
188
  this.#syncStarted = true
210
189
  this.#checkDocUnavailable()
211
190
 
212
- if (doc === undefined) return
191
+ const wasUnavailable = doc === undefined
192
+ if (wasUnavailable && newPeers.size == 0) {
193
+ return
194
+ }
195
+ // If the doc is unavailable we still need a blank document to generate
196
+ // the sync message from
197
+ const theDoc = doc ?? A.init<unknown>()
213
198
 
214
199
  peerIds.forEach(peerId => {
215
- this.#sendSyncMessage(peerId, doc)
200
+ this.#sendSyncMessage(peerId, theDoc)
216
201
  })
217
202
  })
218
203
  }
@@ -222,7 +207,7 @@ export class DocSynchronizer extends Synchronizer {
222
207
  this.#peers = this.#peers.filter(p => p !== peerId)
223
208
  }
224
209
 
225
- receiveMessage(message: SynchronizerMessage) {
210
+ receiveMessage(message: RepoMessage) {
226
211
  switch (message.type) {
227
212
  case "sync":
228
213
  case "request":
@@ -246,7 +231,7 @@ export class DocSynchronizer extends Synchronizer {
246
231
 
247
232
  const { senderId, data } = message
248
233
 
249
- const contents = decode(data)
234
+ const contents = decode(new Uint8Array(data))
250
235
 
251
236
  this.handle.emit("ephemeral-message", {
252
237
  handle: this.handle,
@@ -320,11 +305,12 @@ export class DocSynchronizer extends Synchronizer {
320
305
  this.#peers
321
306
  .filter(peerId => this.#peerDocumentStatuses[peerId] === "wants")
322
307
  .forEach(peerId => {
323
- this.emit("message", {
308
+ const message: MessageContents<DocumentUnavailableMessage> = {
324
309
  type: "doc-unavailable",
325
310
  documentId: this.handle.documentId,
326
311
  targetId: peerId,
327
- })
312
+ }
313
+ this.emit("message", message)
328
314
  })
329
315
 
330
316
  this.handle.unavailable()
@@ -1,8 +1,8 @@
1
1
  import { EventEmitter } from "eventemitter3"
2
- import { Message, MessageContents } from "../network/messages.js"
2
+ import { RepoMessage, MessageContents } from "../network/messages.js"
3
3
 
4
4
  export abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
5
- abstract receiveMessage(message: Message): void
5
+ abstract receiveMessage(message: RepoMessage): void
6
6
  }
7
7
 
8
8
  export interface SynchronizerEvents {
package/src/types.ts CHANGED
@@ -1,17 +1,22 @@
1
1
  /** The ID of a document. Typically you should use a {@link AutomergeUrl} instead.
2
2
  */
3
3
  export type DocumentId = string & { __documentId: true } // for logging
4
- /** A branded string representing a URL for a document
4
+
5
+ /** A branded string representing a URL for a document
5
6
  *
6
7
  * @remarks
7
- * An automerge URL has the form `automerge:<base58 encoded string>`. This
8
+ * An automerge URL has the form `automerge:<base58 encoded string>`. This
8
9
  * type is returned from various routines which validate a url.
9
10
  *
10
11
  */
11
12
  export type AutomergeUrl = string & { __documentUrl: true } // for opening / linking
13
+
12
14
  /** A document ID as a Uint8Array instead of a bas58 encoded string. Typically you should use a {@link AutomergeUrl} instead.
13
15
  */
14
16
  export type BinaryDocumentId = Uint8Array & { __binaryDocumentId: true } // for storing / syncing
15
17
 
16
18
  /** A branded type for peer IDs */
17
- export type PeerId = string & { __peerId: false }
19
+ export type PeerId = string & { __peerId: true }
20
+
21
+ /** A randomly generated string created when the {@link Repo} starts up */
22
+ export type SessionId = string & { __SessionId: true }