@automerge/automerge-repo 1.0.6 → 1.0.8

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 (82) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/DocHandle.d.ts +8 -8
  3. package/dist/DocHandle.d.ts.map +1 -1
  4. package/dist/DocHandle.js +4 -8
  5. package/dist/DocUrl.d.ts.map +1 -1
  6. package/dist/Repo.d.ts +6 -3
  7. package/dist/Repo.d.ts.map +1 -1
  8. package/dist/Repo.js +20 -19
  9. package/dist/helpers/cbor.d.ts +2 -2
  10. package/dist/helpers/cbor.d.ts.map +1 -1
  11. package/dist/helpers/cbor.js +1 -1
  12. package/dist/helpers/debounce.d.ts +14 -0
  13. package/dist/helpers/debounce.d.ts.map +1 -0
  14. package/dist/helpers/debounce.js +21 -0
  15. package/dist/helpers/pause.d.ts.map +1 -1
  16. package/dist/helpers/pause.js +3 -1
  17. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  18. package/dist/helpers/tests/network-adapter-tests.js +2 -2
  19. package/dist/helpers/throttle.d.ts +28 -0
  20. package/dist/helpers/throttle.d.ts.map +1 -0
  21. package/dist/helpers/throttle.js +39 -0
  22. package/dist/index.d.ts +11 -9
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -4
  25. package/dist/network/NetworkAdapter.d.ts +3 -3
  26. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  27. package/dist/network/NetworkSubsystem.d.ts +2 -2
  28. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  29. package/dist/network/NetworkSubsystem.js +30 -18
  30. package/dist/network/messages.d.ts +38 -68
  31. package/dist/network/messages.d.ts.map +1 -1
  32. package/dist/network/messages.js +13 -21
  33. package/dist/storage/StorageSubsystem.d.ts +1 -1
  34. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  35. package/dist/storage/StorageSubsystem.js +9 -9
  36. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -3
  37. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  38. package/dist/synchronizer/CollectionSynchronizer.js +3 -3
  39. package/dist/synchronizer/DocSynchronizer.d.ts +4 -3
  40. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  41. package/dist/synchronizer/DocSynchronizer.js +25 -34
  42. package/dist/synchronizer/Synchronizer.d.ts +2 -2
  43. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  44. package/dist/types.d.ts +5 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/fuzz/fuzz.ts +8 -6
  47. package/fuzz/tsconfig.json +8 -0
  48. package/package.json +5 -13
  49. package/src/DocHandle.ts +12 -15
  50. package/src/DocUrl.ts +3 -1
  51. package/src/Repo.ts +36 -29
  52. package/src/helpers/cbor.ts +4 -4
  53. package/src/helpers/debounce.ts +25 -0
  54. package/src/helpers/headsAreSame.ts +1 -1
  55. package/src/helpers/pause.ts +7 -2
  56. package/src/helpers/tests/network-adapter-tests.ts +3 -3
  57. package/src/helpers/throttle.ts +43 -0
  58. package/src/helpers/withTimeout.ts +2 -2
  59. package/src/index.ts +36 -29
  60. package/src/network/NetworkAdapter.ts +7 -3
  61. package/src/network/NetworkSubsystem.ts +31 -23
  62. package/src/network/messages.ts +88 -151
  63. package/src/storage/StorageSubsystem.ts +12 -12
  64. package/src/synchronizer/CollectionSynchronizer.ts +7 -16
  65. package/src/synchronizer/DocSynchronizer.ts +42 -53
  66. package/src/synchronizer/Synchronizer.ts +2 -2
  67. package/src/types.ts +8 -3
  68. package/test/CollectionSynchronizer.test.ts +58 -53
  69. package/test/DocHandle.test.ts +35 -36
  70. package/test/DocSynchronizer.test.ts +5 -8
  71. package/test/Network.test.ts +1 -0
  72. package/test/Repo.test.ts +229 -158
  73. package/test/StorageSubsystem.test.ts +6 -9
  74. package/test/helpers/DummyNetworkAdapter.ts +9 -4
  75. package/test/helpers/DummyStorageAdapter.ts +4 -2
  76. package/test/tsconfig.json +8 -0
  77. package/typedoc.json +3 -3
  78. package/.mocharc.json +0 -5
  79. package/dist/EphemeralData.d.ts +0 -20
  80. package/dist/EphemeralData.d.ts.map +0 -1
  81. package/dist/EphemeralData.js +0 -1
  82. package/src/EphemeralData.ts +0 -17
package/src/DocHandle.ts CHANGED
@@ -14,12 +14,11 @@ import {
14
14
  TypegenDisabled,
15
15
  } from "xstate"
16
16
  import { waitFor } from "xstate/lib/waitFor.js"
17
- import { headsAreSame } from "./helpers/headsAreSame.js"
18
- import { pause } from "./helpers/pause.js"
19
- import { TimeoutError, withTimeout } from "./helpers/withTimeout.js"
20
- import type { DocumentId, PeerId, AutomergeUrl } from "./types.js"
21
17
  import { stringifyAutomergeUrl } from "./DocUrl.js"
22
18
  import { encode } from "./helpers/cbor.js"
19
+ import { headsAreSame } from "./helpers/headsAreSame.js"
20
+ import { withTimeout } from "./helpers/withTimeout.js"
21
+ import type { AutomergeUrl, DocumentId, PeerId } from "./types.js"
23
22
 
24
23
  /** DocHandle is a wrapper around a single Automerge document that lets us
25
24
  * listen for changes and notify the network and storage of new changes.
@@ -290,14 +289,12 @@ export class DocHandle<T> //
290
289
  async doc(
291
290
  awaitStates: HandleState[] = [READY, UNAVAILABLE]
292
291
  ): Promise<A.Doc<T> | undefined> {
293
- await pause() // yield one tick because reasons
294
292
  try {
295
293
  // wait for the document to enter one of the desired states
296
294
  await this.#statePromise(awaitStates)
297
295
  } catch (error) {
298
- if (error instanceof TimeoutError)
299
- throw new Error(`DocHandle: timed out loading ${this.documentId}`)
300
- else throw error
296
+ // if we timed out (or the load has already failed), return undefined
297
+ return undefined
301
298
  }
302
299
  // Return the document
303
300
  return !this.isUnavailable() ? this.#doc : undefined
@@ -431,7 +428,7 @@ export class DocHandle<T> //
431
428
  * a user could have multiple tabs open and would appear as multiple PeerIds.
432
429
  * every message source must have a unique PeerId.
433
430
  */
434
- broadcast(message: any) {
431
+ broadcast(message: unknown) {
435
432
  this.emit("ephemeral-message-outbound", {
436
433
  handle: this,
437
434
  data: encode(message),
@@ -474,14 +471,14 @@ export interface DocHandleChangePayload<T> {
474
471
  patchInfo: A.PatchInfo<T>
475
472
  }
476
473
 
477
- export interface DocHandleEphemeralMessagePayload {
478
- handle: DocHandle<any>
474
+ export interface DocHandleEphemeralMessagePayload<T> {
475
+ handle: DocHandle<T>
479
476
  senderId: PeerId
480
477
  message: unknown
481
478
  }
482
479
 
483
- export interface DocHandleOutboundEphemeralMessagePayload {
484
- handle: DocHandle<any>
480
+ export interface DocHandleOutboundEphemeralMessagePayload<T> {
481
+ handle: DocHandle<T>
485
482
  data: Uint8Array
486
483
  }
487
484
 
@@ -490,9 +487,9 @@ export interface DocHandleEvents<T> {
490
487
  change: (payload: DocHandleChangePayload<T>) => void
491
488
  delete: (payload: DocHandleDeletePayload<T>) => void
492
489
  unavailable: (payload: DocHandleDeletePayload<T>) => void
493
- "ephemeral-message": (payload: DocHandleEphemeralMessagePayload) => void
490
+ "ephemeral-message": (payload: DocHandleEphemeralMessagePayload<T>) => void
494
491
  "ephemeral-message-outbound": (
495
- payload: DocHandleOutboundEphemeralMessagePayload
492
+ payload: DocHandleOutboundEphemeralMessagePayload<T>
496
493
  ) => void
497
494
  }
498
495
 
package/src/DocUrl.ts CHANGED
@@ -29,7 +29,9 @@ export const parseAutomergeUrl = (url: AutomergeUrl) => {
29
29
  */
30
30
  export const stringifyAutomergeUrl = ({
31
31
  documentId,
32
- }: {documentId: DocumentId | BinaryDocumentId}): AutomergeUrl => {
32
+ }: {
33
+ documentId: DocumentId | BinaryDocumentId
34
+ }): AutomergeUrl => {
33
35
  if (documentId instanceof Uint8Array)
34
36
  return (urlPrefix +
35
37
  binaryToDocumentId(documentId as BinaryDocumentId)) as AutomergeUrl
package/src/Repo.ts CHANGED
@@ -1,21 +1,20 @@
1
+ import { next as Automerge } from "@automerge/automerge"
1
2
  import debug from "debug"
2
- import { NetworkAdapter } from "./network/NetworkAdapter.js"
3
- import { NetworkSubsystem } from "./network/NetworkSubsystem.js"
4
- import { StorageAdapter } from "./storage/StorageAdapter.js"
5
- import { StorageSubsystem } from "./storage/StorageSubsystem.js"
6
- import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
7
- import { type AutomergeUrl, DocumentId, PeerId } from "./types.js"
8
- import { v4 as uuid } from "uuid"
3
+ import { EventEmitter } from "eventemitter3"
4
+ import { DocHandle, DocHandleEncodedChangePayload } from "./DocHandle.js"
9
5
  import {
10
- parseAutomergeUrl,
11
6
  generateAutomergeUrl,
12
7
  isValidAutomergeUrl,
8
+ parseAutomergeUrl,
13
9
  parseLegacyUUID,
14
10
  } from "./DocUrl.js"
15
-
16
- import { DocHandle } from "./DocHandle.js"
17
- import { EventEmitter } from "eventemitter3"
18
- import { next as Automerge } from "@automerge/automerge"
11
+ import { throttle } from "./helpers/throttle.js"
12
+ import { NetworkAdapter } from "./network/NetworkAdapter.js"
13
+ import { NetworkSubsystem } from "./network/NetworkSubsystem.js"
14
+ import { StorageAdapter } from "./storage/StorageAdapter.js"
15
+ import { StorageSubsystem } from "./storage/StorageSubsystem.js"
16
+ import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
17
+ import { DocumentId, PeerId, type AutomergeUrl } from "./types.js"
19
18
 
20
19
  /** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
21
20
  /** The `Repo` is the main entry point of this library
@@ -32,6 +31,11 @@ export class Repo extends EventEmitter<RepoEvents> {
32
31
  networkSubsystem: NetworkSubsystem
33
32
  /** @hidden */
34
33
  storageSubsystem?: StorageSubsystem
34
+
35
+ /** The debounce rate is adjustable on the repo. */
36
+ /** @hidden */
37
+ saveDebounceRate = 100
38
+
35
39
  #handleCache: Record<DocumentId, DocHandle<any>> = {}
36
40
 
37
41
  /** By default, we share generously with all peers. */
@@ -49,10 +53,17 @@ export class Repo extends EventEmitter<RepoEvents> {
49
53
  // up a document by ID. We listen for it in order to wire up storage and network synchronization.
50
54
  this.on("document", async ({ handle, isNew }) => {
51
55
  if (storageSubsystem) {
52
- // Save when the document changes
53
- handle.on("heads-changed", async ({ handle, doc }) => {
54
- await storageSubsystem.saveDoc(handle.documentId, doc)
55
- })
56
+ // Save when the document changes, but no more often than saveDebounceRate.
57
+ const saveFn = ({
58
+ handle,
59
+ doc,
60
+ }: DocHandleEncodedChangePayload<any>) => {
61
+ void storageSubsystem.saveDoc(handle.documentId, doc)
62
+ }
63
+ const debouncedSaveFn = handle.on(
64
+ "heads-changed",
65
+ throttle(saveFn, this.saveDebounceRate)
66
+ )
56
67
 
57
68
  if (isNew) {
58
69
  // this is a new document, immediately save it
@@ -106,9 +117,9 @@ export class Repo extends EventEmitter<RepoEvents> {
106
117
  // The synchronizer uses the network subsystem to keep documents in sync with peers.
107
118
  const synchronizer = new CollectionSynchronizer(this)
108
119
 
109
- // When the synchronizer emits sync messages, send them to peers
120
+ // When the synchronizer emits messages, send them to peers
110
121
  synchronizer.on("message", message => {
111
- this.#log(`sending sync message to ${message.targetId}`)
122
+ this.#log(`sending ${message.type} message to ${message.targetId}`)
112
123
  networkSubsystem.send(message)
113
124
  })
114
125
 
@@ -198,9 +209,9 @@ export class Repo extends EventEmitter<RepoEvents> {
198
209
  * @param clonedHandle - The handle to clone
199
210
  *
200
211
  * @remarks This is a wrapper around the `clone` function in the Automerge library.
201
- * The new `DocHandle` will have a new URL but will share history with the original,
202
- * which means that changes made to the cloned handle can be sensibly merged back
203
- * into the original.
212
+ * The new `DocHandle` will have a new URL but will share history with the original,
213
+ * which means that changes made to the cloned handle can be sensibly merged back
214
+ * into the original.
204
215
  *
205
216
  * Any peers this `Repo` is connected to for whom `sharePolicy` returns `true` will
206
217
  * be notified of the newly created DocHandle.
@@ -223,7 +234,7 @@ export class Repo extends EventEmitter<RepoEvents> {
223
234
 
224
235
  const handle = this.create<T>()
225
236
 
226
- handle.update((doc: Automerge.Doc<T>) => {
237
+ handle.update(() => {
227
238
  // we replace the document with the new cloned one
228
239
  return Automerge.clone(sourceDoc)
229
240
  })
@@ -240,7 +251,7 @@ export class Repo extends EventEmitter<RepoEvents> {
240
251
  automergeUrl: AutomergeUrl
241
252
  ): DocHandle<T> {
242
253
  if (!isValidAutomergeUrl(automergeUrl)) {
243
- let maybeAutomergeUrl = parseLegacyUUID(automergeUrl)
254
+ const maybeAutomergeUrl = parseLegacyUUID(automergeUrl)
244
255
  if (maybeAutomergeUrl) {
245
256
  console.warn(
246
257
  "Legacy UUID document ID detected, converting to AutomergeUrl. This will be removed in a future version."
@@ -274,17 +285,13 @@ export class Repo extends EventEmitter<RepoEvents> {
274
285
  /** The documentId of the handle to delete */
275
286
  id: DocumentId | AutomergeUrl
276
287
  ) {
277
- if (isValidAutomergeUrl(id)) {
278
- ;({ documentId: id } = parseAutomergeUrl(id))
279
- }
288
+ if (isValidAutomergeUrl(id)) id = parseAutomergeUrl(id).documentId
280
289
 
281
290
  const handle = this.#getHandle(id, false)
282
291
  handle.delete()
283
292
 
284
293
  delete this.#handleCache[id]
285
- this.emit("delete-document", {
286
- documentId: id,
287
- })
294
+ this.emit("delete-document", { documentId: id })
288
295
  }
289
296
  }
290
297
 
@@ -1,10 +1,10 @@
1
- import { Encoder, decode as cborXdecode } from "cbor-x";
1
+ import { Encoder, decode as cborXdecode } from "cbor-x"
2
2
 
3
- export function encode(obj: any): Buffer {
4
- let encoder = new Encoder({tagUint8Array: false})
3
+ export function encode(obj: unknown): Buffer {
4
+ const encoder = new Encoder({ tagUint8Array: false })
5
5
  return encoder.encode(obj)
6
6
  }
7
7
 
8
- export function decode(buf: Buffer | Uint8Array): any {
8
+ export function decode<T = unknown>(buf: Buffer | Uint8Array): T {
9
9
  return cborXdecode(buf)
10
10
  }
@@ -0,0 +1,25 @@
1
+ /** throttle( callback, rate )
2
+ * Returns a throttle function with a build in debounce timer that runs after `wait` ms.
3
+ *
4
+ * Note that the args go inside the parameter and you should be careful not to
5
+ * recreate the function on each usage. (In React, see useMemo().)
6
+ *
7
+ *
8
+ * Example usage:
9
+ * const callback = throttle((ev) => { doSomethingExpensiveOrOccasional() }, 100)
10
+ * target.addEventListener('frequent-event', callback);
11
+ *
12
+ */
13
+
14
+ export const throttle = <F extends (...args: Parameters<F>) => ReturnType<F>>(
15
+ fn: F,
16
+ rate: number
17
+ ) => {
18
+ let timeout: ReturnType<typeof setTimeout>
19
+ return function (...args: Parameters<F>) {
20
+ clearTimeout(timeout)
21
+ timeout = setTimeout(() => {
22
+ fn.apply(null, args)
23
+ }, rate)
24
+ }
25
+ }
@@ -1,4 +1,4 @@
1
- import {Heads} from "@automerge/automerge/next"
1
+ import { Heads } from "@automerge/automerge/next"
2
2
  import { arraysAreEqual } from "./arraysAreEqual.js"
3
3
 
4
4
  export const headsAreSame = (a: Heads, b: Heads) => {
@@ -1,9 +1,14 @@
1
1
  export const pause = (t = 0) =>
2
2
  new Promise<void>(resolve => setTimeout(() => resolve(), t))
3
3
 
4
- export function rejectOnTimeout<T>(promise: Promise<T>, millis: number): Promise<T> {
4
+ export function rejectOnTimeout<T>(
5
+ promise: Promise<T>,
6
+ millis: number
7
+ ): Promise<T> {
5
8
  return Promise.race([
6
9
  promise,
7
- pause(millis).then(() => { throw new Error("timeout exceeded") }),
10
+ pause(millis).then(() => {
11
+ throw new Error("timeout exceeded")
12
+ }),
8
13
  ])
9
14
  }
@@ -1,7 +1,7 @@
1
- import { PeerId, Repo, type NetworkAdapter, DocumentId } from "../../index.js"
1
+ import assert from "assert"
2
+ import { describe, it } from "vitest"
3
+ import { PeerId, Repo, type NetworkAdapter } from "../../index.js"
2
4
  import { eventPromise, eventPromises } from "../eventPromise.js"
3
- import { assert } from "chai"
4
- import { describe, it } from "mocha"
5
5
  import { pause } from "../pause.js"
6
6
 
7
7
  /**
@@ -0,0 +1,43 @@
1
+ /** Throttle
2
+ * Returns a function with a built in throttle timer that runs after `delay` ms.
3
+ *
4
+ * This function differs from a conventional `throttle` in that it ensures the final
5
+ * call will also execute and delays sending the first one until `delay` ms to allow
6
+ * additional work to accumulate.
7
+ *
8
+ * Here's a diagram:
9
+ *
10
+ * calls +----++++++-----++----
11
+ * dlay ^--v ^--v^--v ^--v
12
+ * execs ---+----+---+------+--
13
+ *
14
+ * The goal in this design is to create batches of changes without flooding
15
+ * communication or storage systems while still feeling responsive.
16
+ * (By default we communicate at 10hz / every 100ms.)
17
+ *
18
+ * Note that the args go inside the parameter and you should be careful not to
19
+ * recreate the function on each usage. (In React, see useMemo().)
20
+ *
21
+ *
22
+ * Example usage:
23
+ * const callback = debounce((ev) => { doSomethingExpensiveOrOccasional() }, 100)
24
+ * target.addEventListener('frequent-event', callback);
25
+ *
26
+ */
27
+
28
+ export const throttle = <F extends (...args: Parameters<F>) => ReturnType<F>>(
29
+ fn: F,
30
+ delay: number
31
+ ) => {
32
+ let lastCall = Date.now()
33
+ let wait
34
+ let timeout: ReturnType<typeof setTimeout>
35
+ return function (...args: Parameters<F>) {
36
+ wait = lastCall + delay - Date.now()
37
+ clearTimeout(timeout)
38
+ timeout = setTimeout(() => {
39
+ fn.apply(null, args)
40
+ lastCall = Date.now()
41
+ }, wait)
42
+ }
43
+ }
@@ -6,7 +6,7 @@ export const withTimeout = async <T>(
6
6
  promise: Promise<T>,
7
7
  t: number
8
8
  ): Promise<T> => {
9
- let timeoutId: ReturnType<typeof setTimeout>
9
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
10
10
  const timeoutPromise = new Promise<never>((_, reject) => {
11
11
  timeoutId = setTimeout(
12
12
  () => reject(new TimeoutError(`withTimeout: timed out after ${t}ms`)),
@@ -16,7 +16,7 @@ export const withTimeout = async <T>(
16
16
  try {
17
17
  return await Promise.race([promise, timeoutPromise])
18
18
  } finally {
19
- clearTimeout(timeoutId!)
19
+ clearTimeout(timeoutId)
20
20
  }
21
21
  }
22
22
 
package/src/index.ts CHANGED
@@ -16,9 +16,9 @@
16
16
  *
17
17
  * ```typescript
18
18
  * import { Repo } from "@automerge/automerge-repo";
19
- *
19
+ *
20
20
  * const repo = new Repo({
21
- * storage: <storage adapter>,
21
+ * storage: <storage adapter>,
22
22
  * network: [<network adapter>, <network adapter>]
23
23
  * })
24
24
  *
@@ -26,47 +26,54 @@
26
26
  * ```
27
27
  */
28
28
 
29
- export { DocHandle, type HandleState, type DocHandleOptions, type DocHandleEvents } from "./DocHandle.js"
30
- export type {
29
+ export { DocHandle } from "./DocHandle.js"
30
+ export {
31
+ isValidAutomergeUrl,
32
+ parseAutomergeUrl,
33
+ stringifyAutomergeUrl,
34
+ } from "./DocUrl.js"
35
+ export { Repo } from "./Repo.js"
36
+ export { NetworkAdapter } from "./network/NetworkAdapter.js"
37
+ export { isValidRepoMessage } from "./network/messages.js"
38
+ export { StorageAdapter } from "./storage/StorageAdapter.js"
39
+
40
+ /** @hidden **/
41
+ export * as cbor from "./helpers/cbor.js"
42
+
43
+ // types
44
+
45
+ export type {
31
46
  DocHandleChangePayload,
32
47
  DocHandleDeletePayload,
48
+ DocHandleEncodedChangePayload,
33
49
  DocHandleEphemeralMessagePayload,
50
+ DocHandleEvents,
51
+ DocHandleOptions,
34
52
  DocHandleOutboundEphemeralMessagePayload,
35
- DocHandleEncodedChangePayload,
53
+ HandleState,
36
54
  } from "./DocHandle.js"
37
- export { NetworkAdapter } from "./network/NetworkAdapter.js"
38
55
  export type {
56
+ DeleteDocumentPayload,
57
+ DocumentPayload,
58
+ RepoConfig,
59
+ RepoEvents,
60
+ SharePolicy,
61
+ } from "./Repo.js"
62
+ export type {
63
+ NetworkAdapterEvents,
39
64
  OpenPayload,
40
65
  PeerCandidatePayload,
41
66
  PeerDisconnectedPayload,
42
- NetworkAdapterEvents,
43
67
  } from "./network/NetworkAdapter.js"
44
-
45
- // This is a bit confusing right now, but:
46
- // Message is the type for messages used outside of the network adapters
47
- // there are some extra internal network adapter-only messages on NetworkAdapterMessage
48
- // and Message is (as of this writing) a union type for EphmeralMessage and SyncMessage
49
68
  export type {
50
- Message,
51
69
  ArriveMessage,
52
- WelcomeMessage,
53
- NetworkAdapterMessage,
70
+ DocumentUnavailableMessage,
54
71
  EphemeralMessage,
72
+ Message,
73
+ RepoMessage,
55
74
  RequestMessage,
56
- DocumentUnavailableMessage,
57
75
  SyncMessage,
58
- SessionId,
76
+ WelcomeMessage,
59
77
  } from "./network/messages.js"
60
- export { isValidMessage } from "./network/messages.js"
61
-
62
- export { Repo, type SharePolicy, type RepoConfig, type RepoEvents, type DeleteDocumentPayload, type DocumentPayload } from "./Repo.js"
63
- export { StorageAdapter, type StorageKey } from "./storage/StorageAdapter.js"
64
- export {
65
- parseAutomergeUrl,
66
- isValidAutomergeUrl,
67
- stringifyAutomergeUrl as generateAutomergeUrl,
68
- } from "./DocUrl.js"
78
+ export type { StorageKey } from "./storage/StorageAdapter.js"
69
79
  export * from "./types.js"
70
-
71
- /** @hidden **/
72
- export * as cbor from "./helpers/cbor.js"
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from "eventemitter3"
2
2
  import { PeerId } from "../types.js"
3
- import { Message } from "./messages.js"
3
+ import { RepoMessage } from "./messages.js"
4
4
 
5
5
  /** An interface representing some way to connect to other peers
6
6
  *
@@ -22,7 +22,7 @@ export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents>
22
22
  *
23
23
  * @argument message - the message to send
24
24
  */
25
- abstract send(message: Message): void
25
+ abstract send(message: RepoMessage): void
26
26
 
27
27
  /** Called by the {@link Repo} to disconnect from the network */
28
28
  abstract disconnect(): void
@@ -33,14 +33,18 @@ export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents>
33
33
  export interface NetworkAdapterEvents {
34
34
  /** Emitted when the network is ready to be used */
35
35
  ready: (payload: OpenPayload) => void
36
+
36
37
  /** Emitted when the network is closed */
37
38
  close: () => void
39
+
38
40
  /** Emitted when the network adapter learns about a new peer */
39
41
  "peer-candidate": (payload: PeerCandidatePayload) => void
42
+
40
43
  /** Emitted when the network adapter learns that a peer has disconnected */
41
44
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void
45
+
42
46
  /** Emitted when the network adapter receives a message from a peer */
43
- message: (payload: Message) => void
47
+ message: (payload: RepoMessage) => void
44
48
  }
45
49
 
46
50
  export interface OpenPayload {
@@ -1,18 +1,15 @@
1
+ import debug from "debug"
1
2
  import { EventEmitter } from "eventemitter3"
2
- import { PeerId } from "../types.js"
3
+ import { PeerId, SessionId } from "../types.js"
3
4
  import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js"
4
-
5
5
  import {
6
6
  EphemeralMessage,
7
- isEphemeralMessage,
8
- isValidMessage,
9
- Message,
10
7
  MessageContents,
8
+ RepoMessage,
9
+ isEphemeralMessage,
10
+ isValidRepoMessage,
11
11
  } from "./messages.js"
12
12
 
13
- import debug from "debug"
14
- import { SessionId } from "../EphemeralData.js"
15
-
16
13
  type EphemeralMessageSource = `${PeerId}:${SessionId}`
17
14
 
18
15
  const getEphemeralMessageSource = (message: EphemeralMessage) =>
@@ -69,7 +66,7 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
69
66
  })
70
67
 
71
68
  networkAdapter.on("message", msg => {
72
- if (!isValidMessage(msg)) {
69
+ if (!isValidRepoMessage(msg)) {
73
70
  this.#log(`invalid message: ${JSON.stringify(msg)}`)
74
71
  return
75
72
  }
@@ -110,25 +107,36 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
110
107
  this.#log(`Tried to send message but peer not found: ${message.targetId}`)
111
108
  return
112
109
  }
113
- this.#log(`Sending message to ${message.targetId}`)
114
110
 
115
- if (isEphemeralMessage(message)) {
116
- const outbound =
117
- "count" in message
118
- ? message
119
- : {
111
+ /** Messages come in without a senderId and other required information; this is where we make
112
+ * sure they have everything they need.
113
+ */
114
+ const prepareMessage = (message: MessageContents): RepoMessage => {
115
+ if (message.type === "ephemeral") {
116
+ if ("count" in message) {
117
+ // existing ephemeral message from another peer; pass on without changes
118
+ return message as EphemeralMessage
119
+ } else {
120
+ // new ephemeral message from us; add our senderId as well as a counter and session id
121
+ return {
120
122
  ...message,
121
123
  count: ++this.#count,
122
124
  sessionId: this.#sessionId,
123
125
  senderId: this.peerId,
124
- }
125
- this.#log("Ephemeral message", outbound)
126
- peer.send(outbound)
127
- } else {
128
- const outbound = { ...message, senderId: this.peerId }
129
- this.#log("Sync message", outbound)
130
- peer.send(outbound)
126
+ } as EphemeralMessage
127
+ }
128
+ } else {
129
+ // other message type; just add our senderId
130
+ return {
131
+ ...message,
132
+ senderId: this.peerId,
133
+ } as RepoMessage
134
+ }
131
135
  }
136
+
137
+ const outbound = prepareMessage(message)
138
+ this.#log("sending message", outbound)
139
+ peer.send(outbound as RepoMessage)
132
140
  }
133
141
 
134
142
  isReady = () => {
@@ -157,7 +165,7 @@ function randomPeerId() {
157
165
  export interface NetworkSubsystemEvents {
158
166
  peer: (payload: PeerPayload) => void
159
167
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void
160
- message: (payload: Message) => void
168
+ message: (payload: RepoMessage) => void
161
169
  ready: () => void
162
170
  }
163
171