@automerge/automerge-repo 1.1.0-alpha.1 → 1.1.0-alpha.13

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 (58) hide show
  1. package/README.md +12 -7
  2. package/dist/AutomergeUrl.js +2 -2
  3. package/dist/RemoteHeadsSubscriptions.d.ts +1 -0
  4. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
  5. package/dist/RemoteHeadsSubscriptions.js +76 -16
  6. package/dist/Repo.d.ts +23 -10
  7. package/dist/Repo.d.ts.map +1 -1
  8. package/dist/Repo.js +103 -54
  9. package/dist/helpers/debounce.js +1 -1
  10. package/dist/helpers/pause.d.ts.map +1 -1
  11. package/dist/helpers/pause.js +2 -0
  12. package/dist/helpers/throttle.js +1 -1
  13. package/dist/helpers/withTimeout.d.ts.map +1 -1
  14. package/dist/helpers/withTimeout.js +2 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/network/NetworkAdapter.d.ts +14 -7
  19. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  20. package/dist/network/NetworkAdapter.js +3 -3
  21. package/dist/network/NetworkSubsystem.d.ts +4 -8
  22. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  23. package/dist/network/NetworkSubsystem.js +12 -13
  24. package/dist/network/messages.d.ts +48 -38
  25. package/dist/network/messages.d.ts.map +1 -1
  26. package/dist/network/messages.js +7 -9
  27. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  28. package/dist/storage/StorageSubsystem.js +7 -2
  29. package/dist/storage/keyHash.d.ts.map +1 -1
  30. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  31. package/dist/synchronizer/CollectionSynchronizer.js +5 -3
  32. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  33. package/dist/synchronizer/DocSynchronizer.js +20 -8
  34. package/dist/synchronizer/Synchronizer.d.ts +12 -3
  35. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  36. package/package.json +6 -6
  37. package/src/AutomergeUrl.ts +2 -2
  38. package/src/RemoteHeadsSubscriptions.ts +85 -16
  39. package/src/Repo.ts +131 -68
  40. package/src/helpers/debounce.ts +1 -1
  41. package/src/helpers/pause.ts +4 -0
  42. package/src/helpers/throttle.ts +1 -1
  43. package/src/helpers/withTimeout.ts +2 -0
  44. package/src/index.ts +2 -1
  45. package/src/network/NetworkAdapter.ts +18 -12
  46. package/src/network/NetworkSubsystem.ts +23 -24
  47. package/src/network/messages.ts +77 -68
  48. package/src/storage/StorageSubsystem.ts +7 -2
  49. package/src/storage/keyHash.ts +2 -0
  50. package/src/synchronizer/CollectionSynchronizer.ts +7 -4
  51. package/src/synchronizer/DocSynchronizer.ts +27 -15
  52. package/src/synchronizer/Synchronizer.ts +13 -3
  53. package/test/RemoteHeadsSubscriptions.test.ts +34 -24
  54. package/test/Repo.test.ts +57 -2
  55. package/test/StorageSubsystem.test.ts +1 -1
  56. package/test/helpers/waitForMessages.ts +22 -0
  57. package/test/remoteHeads.test.ts +197 -72
  58. package/.eslintrc +0 -28
package/README.md CHANGED
@@ -45,6 +45,10 @@ A `Repo` exposes these methods:
45
45
  networks.
46
46
  - `delete(docId: DocumentId)`
47
47
  Deletes the local copy of a document from the local cache and local storage. _This does not currently delete the document from any other peers_.
48
+ - `import(binary: Uint8Array)`
49
+ Imports a document binary (from `export()` or `Automerge.save(doc)`) into the repo, returning a new handle
50
+ - `export(docId: DocumentId)`
51
+ Exports the document. Returns a Promise containing either the Uint8Array of the document or undefined if the document is currently unavailable. See the [Automerge binary format spec](https://automerge.org/automerge-binary-format-spec/) for more details on the shape of the Uint8Array.
48
52
  - `.on("document", ({handle: DocHandle}) => void)`
49
53
  Registers a callback to be fired each time a new document is loaded or created.
50
54
  - `.on("delete-document", ({handle: DocHandle}) => void)`
@@ -64,7 +68,7 @@ the document.
64
68
 
65
69
  A `DocHandle` also emits these events:
66
70
 
67
- - `change({handle: DocHandle, patches: Patch[], patchInfo: PatchInfo})`
71
+ - `change({handle: DocHandle, patches: Patch[], patchInfo: PatchInfo})`
68
72
  Called whenever the document changes, the handle's .doc
69
73
  - `delete`
70
74
  Called when the document is deleted locally.
@@ -85,11 +89,12 @@ network adapter:
85
89
  const repo = new Repo({
86
90
  network: [new BroadcastChannelNetworkAdapter()],
87
91
  storage: new IndexedDBStorageAdapter(),
88
- sharePolicy: async (peerId: PeerId, documentId: DocumentId) => true // this is the default
92
+ sharePolicy: async (peerId: PeerId, documentId: DocumentId) => true, // this is the default
89
93
  })
90
94
  ```
91
95
 
92
96
  ### Share Policy
97
+
93
98
  The share policy is used to determine which document in your repo should be _automatically_ shared with other peers. **The default setting is to share all documents with all peers.**
94
99
 
95
100
  > **Warning**
@@ -99,7 +104,6 @@ You can override this by providing a custom share policy. The function should re
99
104
 
100
105
  The share policy will not stop a document being _requested_ by another peer by its `DocumentId`.
101
106
 
102
- ```ts
103
107
  ## Starting the demo app
104
108
 
105
109
  ```bash
@@ -272,7 +276,8 @@ you'll need to manually copy the `rootDocId` value between the browsers.)
272
276
  Originally authored by Peter van Hardenberg.
273
277
 
274
278
  With gratitude for contributions by:
275
- - Herb Caudill
276
- - Jeremy Rose
277
- - Alex Currie-Clark
278
- - Dylan Mackenzie
279
+
280
+ - Herb Caudill
281
+ - Jeremy Rose
282
+ - Alex Currie-Clark
283
+ - Dylan Mackenzie
@@ -4,7 +4,7 @@ export const urlPrefix = "automerge:";
4
4
  /** Given an Automerge URL, returns the DocumentId in both base58check-encoded form and binary form */
5
5
  export const parseAutomergeUrl = (url) => {
6
6
  const regex = new RegExp(`^${urlPrefix}(\\w+)$`);
7
- const [_, docMatch] = url.match(regex) || [];
7
+ const [, docMatch] = url.match(regex) || [];
8
8
  const documentId = docMatch;
9
9
  const binaryDocumentId = documentIdToBinary(documentId);
10
10
  if (!binaryDocumentId)
@@ -21,7 +21,7 @@ export const parseAutomergeUrl = (url) => {
21
21
  * Throws on invalid input.
22
22
  */
23
23
  export const stringifyAutomergeUrl = (arg) => {
24
- let documentId = arg instanceof Uint8Array || typeof arg === "string"
24
+ const documentId = arg instanceof Uint8Array || typeof arg === "string"
25
25
  ? arg
26
26
  : "documentId" in arg
27
27
  ? arg.documentId
@@ -36,6 +36,7 @@ export declare class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSu
36
36
  handleImmediateRemoteHeadsChanged(documentId: DocumentId, storageId: StorageId, heads: A.Heads): void;
37
37
  addGenerousPeer(peerId: PeerId): void;
38
38
  removePeer(peerId: PeerId): void;
39
+ subscribePeerToDoc(peerId: PeerId, documentId: DocumentId): void;
39
40
  }
40
41
  export {};
41
42
  //# sourceMappingURL=RemoteHeadsSubscriptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;IAWvF,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE;IAkBvC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE;IAsB3C,oBAAoB,CAAC,OAAO,EAAE,gCAAgC;IA4C9D,sEAAsE;IACtE,iBAAiB,CAAC,GAAG,EAAE,kBAAkB;IA8CzC,kEAAkE;IAClE,iCAAiC,CAC/B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,CAAC,KAAK;IA8BhB,eAAe,CAAC,MAAM,EAAE,MAAM;IAwB9B,UAAU,CAAC,MAAM,EAAE,MAAM;CA+D1B"}
1
+ {"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;IAcvF,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE;IAkBvC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE;IAsB3C,oBAAoB,CAAC,OAAO,EAAE,gCAAgC;IA0E9D,sEAAsE;IACtE,iBAAiB,CAAC,GAAG,EAAE,kBAAkB;IAgDzC,kEAAkE;IAClE,iCAAiC,CAC/B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,CAAC,KAAK;IAgChB,eAAe,CAAC,MAAM,EAAE,MAAM;IAwB9B,UAAU,CAAC,MAAM,EAAE,MAAM;IA2BzB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU;CAoE1D"}
@@ -9,6 +9,8 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
9
9
  #theirSubscriptions = new Map();
10
10
  // Peers we will always share remote heads with even if they are not subscribed
11
11
  #generousPeers = new Set();
12
+ // Documents each peer has open, we need this information so we only send remote heads of documents that the peer knows
13
+ #subscribedDocsByPeer = new Map();
12
14
  #log = debug("automerge-repo:remote-heads-subscriptions");
13
15
  subscribeToRemotes(remotes) {
14
16
  this.#log("subscribeToRemotes", remotes);
@@ -47,10 +49,14 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
47
49
  handleControlMessage(control) {
48
50
  const remotesToAdd = [];
49
51
  const remotesToRemove = [];
52
+ const addedRemotesWeKnow = [];
50
53
  this.#log("handleControlMessage", control);
51
54
  if (control.add) {
52
55
  for (const remote of control.add) {
53
56
  let theirSubs = this.#theirSubscriptions.get(remote);
57
+ if (this.#ourSubscriptions.has(remote) || theirSubs) {
58
+ addedRemotesWeKnow.push(remote);
59
+ }
54
60
  if (!theirSubs) {
55
61
  theirSubs = new Set();
56
62
  this.#theirSubscriptions.set(remote, theirSubs);
@@ -80,6 +86,28 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
80
86
  remove: remotesToRemove,
81
87
  });
82
88
  }
89
+ // send all our stored heads of documents the peer knows for the remotes they've added
90
+ for (const remote of addedRemotesWeKnow) {
91
+ const subscribedDocs = this.#subscribedDocsByPeer.get(control.senderId);
92
+ if (subscribedDocs) {
93
+ for (const documentId of subscribedDocs) {
94
+ const knownHeads = this.#knownHeads.get(documentId);
95
+ if (!knownHeads) {
96
+ continue;
97
+ }
98
+ const lastHeads = knownHeads.get(remote);
99
+ if (lastHeads) {
100
+ this.emit("notify-remote-heads", {
101
+ targetId: control.senderId,
102
+ documentId,
103
+ heads: lastHeads.heads,
104
+ timestamp: lastHeads.timestamp,
105
+ storageId: remote,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
83
111
  }
84
112
  /** A peer we are not directly connected to has changed their heads */
85
113
  handleRemoteHeads(msg) {
@@ -112,13 +140,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
112
140
  const theirSubs = this.#theirSubscriptions.get(event.storageId);
113
141
  if (theirSubs) {
114
142
  for (const peerId of theirSubs) {
115
- this.emit("notify-remote-heads", {
116
- targetId: peerId,
117
- documentId: event.documentId,
118
- heads: event.remoteHeads,
119
- timestamp: event.timestamp,
120
- storageId: event.storageId,
121
- });
143
+ if (this.#isPeerSubscribedToDoc(peerId, event.documentId)) {
144
+ this.emit("notify-remote-heads", {
145
+ targetId: peerId,
146
+ documentId: event.documentId,
147
+ heads: event.remoteHeads,
148
+ timestamp: event.timestamp,
149
+ storageId: event.storageId,
150
+ });
151
+ }
122
152
  }
123
153
  }
124
154
  }
@@ -140,13 +170,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
140
170
  const theirSubs = this.#theirSubscriptions.get(storageId);
141
171
  if (theirSubs) {
142
172
  for (const peerId of theirSubs) {
143
- this.emit("notify-remote-heads", {
144
- targetId: peerId,
145
- documentId: documentId,
146
- heads: heads,
147
- timestamp: timestamp,
148
- storageId: storageId,
149
- });
173
+ if (this.#isPeerSubscribedToDoc(peerId, documentId)) {
174
+ this.emit("notify-remote-heads", {
175
+ targetId: peerId,
176
+ documentId: documentId,
177
+ heads: heads,
178
+ timestamp: timestamp,
179
+ storageId: storageId,
180
+ });
181
+ }
150
182
  }
151
183
  }
152
184
  }
@@ -175,6 +207,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
175
207
  this.#log("removePeer", peerId);
176
208
  const remotesToRemove = [];
177
209
  this.#generousPeers.delete(peerId);
210
+ this.#subscribedDocsByPeer.delete(peerId);
178
211
  for (const [storageId, peerIds] of this.#theirSubscriptions) {
179
212
  if (peerIds.has(peerId)) {
180
213
  peerIds.delete(peerId);
@@ -191,6 +224,33 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
191
224
  });
192
225
  }
193
226
  }
227
+ subscribePeerToDoc(peerId, documentId) {
228
+ let subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
229
+ if (!subscribedDocs) {
230
+ subscribedDocs = new Set();
231
+ this.#subscribedDocsByPeer.set(peerId, subscribedDocs);
232
+ }
233
+ subscribedDocs.add(documentId);
234
+ const remoteHeads = this.#knownHeads.get(documentId);
235
+ if (remoteHeads) {
236
+ for (const [storageId, lastHeads] of remoteHeads) {
237
+ const subscribedPeers = this.#theirSubscriptions.get(storageId);
238
+ if (subscribedPeers && subscribedPeers.has(peerId)) {
239
+ this.emit("notify-remote-heads", {
240
+ targetId: peerId,
241
+ documentId,
242
+ heads: lastHeads.heads,
243
+ timestamp: lastHeads.timestamp,
244
+ storageId,
245
+ });
246
+ }
247
+ }
248
+ }
249
+ }
250
+ #isPeerSubscribedToDoc(peerId, documentId) {
251
+ const subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
252
+ return subscribedDocs && subscribedDocs.has(documentId);
253
+ }
194
254
  /** Returns the (document, storageId) pairs which have changed after processing msg */
195
255
  #changedHeads(msg) {
196
256
  const changedHeads = [];
@@ -202,11 +262,11 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
202
262
  }
203
263
  let remote = this.#knownHeads.get(documentId);
204
264
  if (!remote) {
205
- remote = new Map([[storageId, { heads, timestamp }]]);
265
+ remote = new Map();
206
266
  this.#knownHeads.set(documentId, remote);
207
267
  }
208
268
  const docRemote = remote.get(storageId);
209
- if (docRemote && docRemote.timestamp > timestamp) {
269
+ if (docRemote && docRemote.timestamp >= timestamp) {
210
270
  continue;
211
271
  }
212
272
  else {
package/dist/Repo.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
2
  import { DocHandle } from "./DocHandle.js";
3
- import { NetworkAdapter } from "./network/NetworkAdapter.js";
3
+ import { NetworkAdapter, type PeerMetadata } from "./network/NetworkAdapter.js";
4
4
  import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
5
5
  import { StorageAdapter } from "./storage/StorageAdapter.js";
6
6
  import { StorageSubsystem } from "./storage/StorageSubsystem.js";
7
- import type { AnyDocumentId, DocumentId, PeerId } from "./types.js";
8
7
  import { StorageId } from "./storage/types.js";
8
+ import type { AnyDocumentId, DocumentId, PeerId } from "./types.js";
9
9
  /** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
10
10
  /** The `Repo` is the main entry point of this library
11
11
  *
@@ -26,14 +26,15 @@ export declare class Repo extends EventEmitter<RepoEvents> {
26
26
  /** By default, we share generously with all peers. */
27
27
  /** @hidden */
28
28
  sharePolicy: SharePolicy;
29
- /** maps peer id to to persistance information (storageId, isEphemeral), access by collection synchronizer */
29
+ /** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
30
30
  /** @hidden */
31
- persistanceInfoByPeerId: Record<PeerId, PersistanceInfo>;
32
- constructor({ storage, network, peerId, sharePolicy, isEphemeral, }: RepoConfig);
31
+ peerMetadataByPeerId: Record<PeerId, PeerMetadata>;
32
+ constructor({ storage, network, peerId, sharePolicy, isEphemeral, enableRemoteHeadsGossiping, }: RepoConfig);
33
33
  /** Returns all the handles we have cached. */
34
34
  get handles(): Record<DocumentId, DocHandle<any>>;
35
35
  /** Returns a list of all connected peer ids */
36
36
  get peers(): PeerId[];
37
+ getStorageIdOfPeer(peerId: PeerId): StorageId | undefined;
37
38
  /**
38
39
  * Creates a new document and returns a handle to it. The initial value of the document is
39
40
  * an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
@@ -66,13 +67,22 @@ export declare class Repo extends EventEmitter<RepoEvents> {
66
67
  delete(
67
68
  /** The url or documentId of the handle to delete */
68
69
  id: AnyDocumentId): void;
70
+ /**
71
+ * Exports a document to a binary format.
72
+ * @param id - The url or documentId of the handle to export
73
+ *
74
+ * @returns Promise<Uint8Array | undefined> - A Promise containing the binary document,
75
+ * or undefined if the document is unavailable.
76
+ */
77
+ export(id: AnyDocumentId): Promise<Uint8Array | undefined>;
78
+ /**
79
+ * Imports document binary into the repo.
80
+ * @param binary - The binary to import
81
+ */
82
+ import<T>(binary: Uint8Array): DocHandle<T>;
69
83
  subscribeToRemotes: (remotes: StorageId[]) => void;
70
84
  storageId: () => Promise<StorageId | undefined>;
71
85
  }
72
- interface PersistanceInfo {
73
- storageId: StorageId;
74
- isEphemeral: boolean;
75
- }
76
86
  export interface RepoConfig {
77
87
  /** Our unique identifier */
78
88
  peerId?: PeerId;
@@ -88,6 +98,10 @@ export interface RepoConfig {
88
98
  * all peers). A server only syncs documents that a peer explicitly requests by ID.
89
99
  */
90
100
  sharePolicy?: SharePolicy;
101
+ /**
102
+ * Whether to enable the experimental remote heads gossiping feature
103
+ */
104
+ enableRemoteHeadsGossiping?: boolean;
91
105
  }
92
106
  /** A function that determines whether we should share a document with a peer
93
107
  *
@@ -113,5 +127,4 @@ export interface DocumentPayload {
113
127
  export interface DeleteDocumentPayload {
114
128
  documentId: DocumentId;
115
129
  }
116
- export {};
117
130
  //# sourceMappingURL=Repo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,OAAO,EAAE,SAAS,EAAiC,MAAM,gBAAgB,CAAA;AAIzE,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAG9C,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnE,8FAA8F;AAC9F;;;;;;GAMG;AACH,qBAAa,IAAK,SAAQ,YAAY,CAAC,UAAU,CAAC;;IAGhD,cAAc;IACd,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc;IACd,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,mDAAmD;IACnD,cAAc;IACd,gBAAgB,SAAM;IAMtB,sDAAsD;IACtD,cAAc;IACd,WAAW,EAAE,WAAW,CAAmB;IAE3C,8GAA8G;IAC9G,cAAc;IACd,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAK;gBAK3C,EACV,OAAO,EACP,OAAO,EACP,MAAM,EACN,WAAW,EACX,WAAmC,EACnC,0BAAkC,GACnC,EAAE,UAAU;IAsRb,8CAA8C;IAC9C,IAAI,OAAO,uCAEV;IAED,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAED,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIzD;;;;OAIG;IACH,MAAM,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;IA0BzB;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;IAuBnC;;;OAGG;IACH,IAAI,CAAC,CAAC;IACJ,sDAAsD;IACtD,EAAE,EAAE,aAAa,GAChB,SAAS,CAAC,CAAC,CAAC;IAqBf,MAAM;IACJ,oDAAoD;IACpD,EAAE,EAAE,aAAa;IAWnB;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAShE;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU;IAY5B,kBAAkB,YAAa,SAAS,EAAE,UASzC;IAED,SAAS,QAAa,QAAQ,SAAS,GAAG,SAAS,CAAC,CAMnD;CACF;AAED,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;8DAC0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB,gDAAgD;IAChD,OAAO,CAAC,EAAE,cAAc,CAAA;IAExB,oDAAoD;IACpD,OAAO,EAAE,cAAc,EAAE,CAAA;IAEzB;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IAEzB;;OAEG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAA;CACrC;AAED;;;;;;;KAOK;AACL,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,KACpB,OAAO,CAAC,OAAO,CAAC,CAAA;AAGrB,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC,6BAA6B;IAC7B,iBAAiB,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IACvD,4FAA4F;IAC5F,sBAAsB,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAC7D;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,UAAU,CAAA;CACvB"}
package/dist/Repo.js CHANGED
@@ -3,12 +3,12 @@ import debug from "debug";
3
3
  import { EventEmitter } from "eventemitter3";
4
4
  import { generateAutomergeUrl, interpretAsDocumentId, parseAutomergeUrl, } from "./AutomergeUrl.js";
5
5
  import { DocHandle } from "./DocHandle.js";
6
+ import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
7
+ import { headsAreSame } from "./helpers/headsAreSame.js";
6
8
  import { throttle } from "./helpers/throttle.js";
7
9
  import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
8
10
  import { StorageSubsystem } from "./storage/StorageSubsystem.js";
9
11
  import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
10
- import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
11
- import { headsAreSame } from "./helpers/headsAreSame.js";
12
12
  /** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
13
13
  /** The `Repo` is the main entry point of this library
14
14
  *
@@ -31,12 +31,14 @@ export class Repo extends EventEmitter {
31
31
  /** By default, we share generously with all peers. */
32
32
  /** @hidden */
33
33
  sharePolicy = async () => true;
34
- /** maps peer id to to persistance information (storageId, isEphemeral), access by collection synchronizer */
34
+ /** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
35
35
  /** @hidden */
36
- persistanceInfoByPeerId = {};
36
+ peerMetadataByPeerId = {};
37
37
  #remoteHeadsSubscriptions = new RemoteHeadsSubscriptions();
38
- constructor({ storage, network, peerId, sharePolicy, isEphemeral = storage === undefined, }) {
38
+ #remoteHeadsGossipingEnabled = false;
39
+ constructor({ storage, network, peerId, sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, }) {
39
40
  super();
41
+ this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
40
42
  this.#log = debug(`automerge-repo:repo`);
41
43
  this.sharePolicy = sharePolicy ?? this.sharePolicy;
42
44
  // DOC COLLECTION
@@ -48,7 +50,7 @@ export class Repo extends EventEmitter {
48
50
  const saveFn = ({ handle, doc, }) => {
49
51
  void storageSubsystem.saveDoc(handle.documentId, doc);
50
52
  };
51
- const debouncedSaveFn = handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
53
+ handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
52
54
  if (isNew) {
53
55
  // this is a new document, immediately save it
54
56
  await storageSubsystem.saveDoc(handle.documentId, handle.docSync());
@@ -101,26 +103,34 @@ export class Repo extends EventEmitter {
101
103
  this.#log(`sending ${message.type} message to ${message.targetId}`);
102
104
  networkSubsystem.send(message);
103
105
  });
106
+ if (this.#remoteHeadsGossipingEnabled) {
107
+ this.#synchronizer.on("open-doc", ({ peerId, documentId }) => {
108
+ this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
109
+ });
110
+ }
104
111
  // STORAGE
105
112
  // The storage subsystem has access to some form of persistence, and deals with save and loading documents.
106
113
  const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
107
114
  this.storageSubsystem = storageSubsystem;
108
115
  // NETWORK
109
116
  // The network subsystem deals with sending and receiving messages to and from peers.
110
- const networkSubsystem = new NetworkSubsystem(network, peerId, storageSubsystem?.id() ?? Promise.resolve(undefined), isEphemeral);
117
+ const myPeerMetadata = new Promise(
118
+ // eslint-disable-next-line no-async-promise-executor -- TODO: fix
119
+ async (resolve) => resolve({
120
+ storageId: await storageSubsystem?.id(),
121
+ isEphemeral,
122
+ }));
123
+ const networkSubsystem = new NetworkSubsystem(network, peerId, myPeerMetadata);
111
124
  this.networkSubsystem = networkSubsystem;
112
125
  // When we get a new peer, register it with the synchronizer
113
- networkSubsystem.on("peer", async ({ peerId, storageId, isEphemeral }) => {
126
+ networkSubsystem.on("peer", async ({ peerId, peerMetadata }) => {
114
127
  this.#log("peer connected", { peerId });
115
- if (storageId) {
116
- this.persistanceInfoByPeerId[peerId] = {
117
- storageId,
118
- isEphemeral,
119
- };
128
+ if (peerMetadata) {
129
+ this.peerMetadataByPeerId[peerId] = { ...peerMetadata };
120
130
  }
121
131
  this.sharePolicy(peerId)
122
132
  .then(shouldShare => {
123
- if (shouldShare) {
133
+ if (shouldShare && this.#remoteHeadsGossipingEnabled) {
124
134
  this.#remoteHeadsSubscriptions.addGenerousPeer(peerId);
125
135
  }
126
136
  })
@@ -141,57 +151,62 @@ export class Repo extends EventEmitter {
141
151
  this.#synchronizer.on("sync-state", message => {
142
152
  this.#saveSyncState(message);
143
153
  const handle = this.#handleCache[message.documentId];
144
- const info = this.persistanceInfoByPeerId[message.peerId];
145
- if (!info) {
154
+ const { storageId } = this.peerMetadataByPeerId[message.peerId] || {};
155
+ if (!storageId) {
146
156
  return;
147
157
  }
148
- const { storageId } = info;
149
158
  const heads = handle.getRemoteHeads(storageId);
150
159
  const haveHeadsChanged = message.syncState.theirHeads &&
151
160
  (!heads || !headsAreSame(heads, message.syncState.theirHeads));
152
161
  if (haveHeadsChanged) {
153
162
  handle.setRemoteHeads(storageId, message.syncState.theirHeads);
154
- if (storageId) {
163
+ if (storageId && this.#remoteHeadsGossipingEnabled) {
155
164
  this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(message.documentId, storageId, message.syncState.theirHeads);
156
165
  }
157
166
  }
158
167
  });
159
- this.#remoteHeadsSubscriptions.on("notify-remote-heads", message => {
160
- this.networkSubsystem.send({
161
- type: "remote-heads-changed",
162
- targetId: message.targetId,
163
- documentId: message.documentId,
164
- newHeads: {
165
- [message.storageId]: {
166
- heads: message.heads,
167
- timestamp: message.timestamp,
168
- },
169
- },
170
- });
171
- });
172
- this.#remoteHeadsSubscriptions.on("change-remote-subs", message => {
173
- this.#log("change-remote-subs", message);
174
- for (const peer of message.peers) {
168
+ if (this.#remoteHeadsGossipingEnabled) {
169
+ this.#remoteHeadsSubscriptions.on("notify-remote-heads", message => {
175
170
  this.networkSubsystem.send({
176
- type: "remote-subscription-change",
177
- targetId: peer,
178
- add: message.add,
179
- remove: message.remove,
171
+ type: "remote-heads-changed",
172
+ targetId: message.targetId,
173
+ documentId: message.documentId,
174
+ newHeads: {
175
+ [message.storageId]: {
176
+ heads: message.heads,
177
+ timestamp: message.timestamp,
178
+ },
179
+ },
180
180
  });
181
- }
182
- });
183
- this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
184
- const handle = this.#handleCache[message.documentId];
185
- handle.setRemoteHeads(message.storageId, message.remoteHeads);
186
- });
181
+ });
182
+ this.#remoteHeadsSubscriptions.on("change-remote-subs", message => {
183
+ this.#log("change-remote-subs", message);
184
+ for (const peer of message.peers) {
185
+ this.networkSubsystem.send({
186
+ type: "remote-subscription-change",
187
+ targetId: peer,
188
+ add: message.add,
189
+ remove: message.remove,
190
+ });
191
+ }
192
+ });
193
+ this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
194
+ const handle = this.#handleCache[message.documentId];
195
+ handle.setRemoteHeads(message.storageId, message.remoteHeads);
196
+ });
197
+ }
187
198
  }
188
199
  #receiveMessage(message) {
189
200
  switch (message.type) {
190
201
  case "remote-subscription-change":
191
- this.#remoteHeadsSubscriptions.handleControlMessage(message);
202
+ if (this.#remoteHeadsGossipingEnabled) {
203
+ this.#remoteHeadsSubscriptions.handleControlMessage(message);
204
+ }
192
205
  break;
193
206
  case "remote-heads-changed":
194
- this.#remoteHeadsSubscriptions.handleRemoteHeads(message);
207
+ if (this.#remoteHeadsGossipingEnabled) {
208
+ this.#remoteHeadsSubscriptions.handleRemoteHeads(message);
209
+ }
195
210
  break;
196
211
  case "sync":
197
212
  case "request":
@@ -204,22 +219,21 @@ export class Repo extends EventEmitter {
204
219
  }
205
220
  #throttledSaveSyncStateHandlers = {};
206
221
  /** saves sync state throttled per storage id, if a peer doesn't have a storage id it's sync state is not persisted */
207
- #saveSyncState(message) {
222
+ #saveSyncState(payload) {
208
223
  if (!this.storageSubsystem) {
209
224
  return;
210
225
  }
211
- const persistanceInfo = this.persistanceInfoByPeerId[message.peerId];
212
- if (!persistanceInfo || persistanceInfo.isEphemeral) {
226
+ const { storageId, isEphemeral } = this.peerMetadataByPeerId[payload.peerId] || {};
227
+ if (!storageId || isEphemeral) {
213
228
  return;
214
229
  }
215
- const { storageId } = persistanceInfo;
216
230
  let handler = this.#throttledSaveSyncStateHandlers[storageId];
217
231
  if (!handler) {
218
232
  handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(({ documentId, syncState }) => {
219
- this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
233
+ void this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
220
234
  }, this.saveDebounceRate);
221
235
  }
222
- handler(message);
236
+ handler(payload);
223
237
  }
224
238
  /** Returns an existing handle if we have it; creates one otherwise. */
225
239
  #getHandle(
@@ -245,6 +259,9 @@ export class Repo extends EventEmitter {
245
259
  get peers() {
246
260
  return this.#synchronizer.peers;
247
261
  }
262
+ getStorageIdOfPeer(peerId) {
263
+ return this.peerMetadataByPeerId[peerId]?.storageId;
264
+ }
248
265
  /**
249
266
  * Creates a new document and returns a handle to it. The initial value of the document is
250
267
  * an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
@@ -335,9 +352,41 @@ export class Repo extends EventEmitter {
335
352
  delete this.#handleCache[documentId];
336
353
  this.emit("delete-document", { documentId });
337
354
  }
355
+ /**
356
+ * Exports a document to a binary format.
357
+ * @param id - The url or documentId of the handle to export
358
+ *
359
+ * @returns Promise<Uint8Array | undefined> - A Promise containing the binary document,
360
+ * or undefined if the document is unavailable.
361
+ */
362
+ async export(id) {
363
+ const documentId = interpretAsDocumentId(id);
364
+ const handle = this.#getHandle(documentId, false);
365
+ const doc = await handle.doc();
366
+ if (!doc)
367
+ return undefined;
368
+ return Automerge.save(doc);
369
+ }
370
+ /**
371
+ * Imports document binary into the repo.
372
+ * @param binary - The binary to import
373
+ */
374
+ import(binary) {
375
+ const doc = Automerge.load(binary);
376
+ const handle = this.create();
377
+ handle.update(() => {
378
+ return Automerge.clone(doc);
379
+ });
380
+ return handle;
381
+ }
338
382
  subscribeToRemotes = (remotes) => {
339
- this.#log("subscribeToRemotes", { remotes });
340
- this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes);
383
+ if (this.#remoteHeadsGossipingEnabled) {
384
+ this.#log("subscribeToRemotes", { remotes });
385
+ this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes);
386
+ }
387
+ else {
388
+ this.#log("WARN: subscribeToRemotes called but remote heads gossiping is not enabled");
389
+ }
341
390
  };
342
391
  storageId = async () => {
343
392
  if (!this.storageSubsystem) {
@@ -15,7 +15,7 @@ export const throttle = (fn, rate) => {
15
15
  return function (...args) {
16
16
  clearTimeout(timeout);
17
17
  timeout = setTimeout(() => {
18
- fn.apply(null, args);
18
+ fn(...args);
19
19
  }, rate);
20
20
  };
21
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,+BAC4C,CAAA;AAE9D,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,CAOZ"}
1
+ {"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK,+BAC4C,CAAA;AAE9D,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -1,3 +1,4 @@
1
+ /* c8 ignore start */
1
2
  export const pause = (t = 0) => new Promise(resolve => setTimeout(() => resolve(), t));
2
3
  export function rejectOnTimeout(promise, millis) {
3
4
  return Promise.race([
@@ -7,3 +8,4 @@ export function rejectOnTimeout(promise, millis) {
7
8
  }),
8
9
  ]);
9
10
  }
11
+ /* c8 ignore end */
@@ -32,7 +32,7 @@ export const throttle = (fn, delay) => {
32
32
  wait = lastCall + delay - Date.now();
33
33
  clearTimeout(timeout);
34
34
  timeout = setTimeout(() => {
35
- fn.apply(null, args);
35
+ fn(...args);
36
36
  lastCall = Date.now();
37
37
  }, wait);
38
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW,8BAEnB,MAAM,eAcV,CAAA;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B"}
1
+ {"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,eAAO,MAAM,WAAW,8BAEnB,MAAM,eAcV,CAAA;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B"}
@@ -1,3 +1,4 @@
1
+ /* c8 ignore start */
1
2
  /**
2
3
  * If `promise` is resolved before `t` ms elapse, the timeout is cleared and the result of the
3
4
  * promise is returned. If the timeout ends first, a `TimeoutError` is thrown.
@@ -20,3 +21,4 @@ export class TimeoutError extends Error {
20
21
  this.name = "TimeoutError";
21
22
  }
22
23
  }
24
+ /* c8 ignore end */