@automerge/automerge-repo 1.0.19 → 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 (68) hide show
  1. package/README.md +12 -7
  2. package/dist/AutomergeUrl.js +2 -2
  3. package/dist/DocHandle.d.ts +6 -5
  4. package/dist/DocHandle.d.ts.map +1 -1
  5. package/dist/DocHandle.js +7 -7
  6. package/dist/RemoteHeadsSubscriptions.d.ts +42 -0
  7. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -0
  8. package/dist/RemoteHeadsSubscriptions.js +284 -0
  9. package/dist/Repo.d.ts +29 -2
  10. package/dist/Repo.d.ts.map +1 -1
  11. package/dist/Repo.js +168 -9
  12. package/dist/helpers/debounce.js +1 -1
  13. package/dist/helpers/pause.d.ts.map +1 -1
  14. package/dist/helpers/pause.js +2 -0
  15. package/dist/helpers/throttle.js +1 -1
  16. package/dist/helpers/withTimeout.d.ts.map +1 -1
  17. package/dist/helpers/withTimeout.js +2 -0
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/network/NetworkAdapter.d.ts +15 -1
  22. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  23. package/dist/network/NetworkAdapter.js +3 -1
  24. package/dist/network/NetworkSubsystem.d.ts +4 -2
  25. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  26. package/dist/network/NetworkSubsystem.js +13 -7
  27. package/dist/network/messages.d.ts +68 -35
  28. package/dist/network/messages.d.ts.map +1 -1
  29. package/dist/network/messages.js +9 -7
  30. package/dist/storage/StorageSubsystem.d.ts +5 -3
  31. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  32. package/dist/storage/StorageSubsystem.js +23 -5
  33. package/dist/storage/keyHash.d.ts.map +1 -1
  34. package/dist/storage/types.d.ts +4 -0
  35. package/dist/storage/types.d.ts.map +1 -1
  36. package/dist/synchronizer/CollectionSynchronizer.d.ts +2 -2
  37. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  38. package/dist/synchronizer/CollectionSynchronizer.js +9 -3
  39. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  40. package/dist/synchronizer/DocSynchronizer.js +20 -17
  41. package/dist/synchronizer/Synchronizer.d.ts +12 -3
  42. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  43. package/package.json +6 -6
  44. package/src/AutomergeUrl.ts +2 -2
  45. package/src/DocHandle.ts +10 -9
  46. package/src/RemoteHeadsSubscriptions.ts +375 -0
  47. package/src/Repo.ts +241 -16
  48. package/src/helpers/debounce.ts +1 -1
  49. package/src/helpers/pause.ts +4 -0
  50. package/src/helpers/throttle.ts +1 -1
  51. package/src/helpers/withTimeout.ts +2 -0
  52. package/src/index.ts +3 -1
  53. package/src/network/NetworkAdapter.ts +19 -2
  54. package/src/network/NetworkSubsystem.ts +21 -9
  55. package/src/network/messages.ts +88 -50
  56. package/src/storage/StorageSubsystem.ts +30 -7
  57. package/src/storage/keyHash.ts +2 -0
  58. package/src/storage/types.ts +3 -0
  59. package/src/synchronizer/CollectionSynchronizer.ts +13 -5
  60. package/src/synchronizer/DocSynchronizer.ts +27 -27
  61. package/src/synchronizer/Synchronizer.ts +13 -3
  62. package/test/DocHandle.test.ts +0 -17
  63. package/test/RemoteHeadsSubscriptions.test.ts +353 -0
  64. package/test/Repo.test.ts +108 -17
  65. package/test/StorageSubsystem.test.ts +29 -7
  66. package/test/helpers/waitForMessages.ts +22 -0
  67. package/test/remoteHeads.test.ts +260 -0
  68. package/.eslintrc +0 -28
package/dist/Repo.js CHANGED
@@ -3,6 +3,8 @@ 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";
@@ -29,8 +31,14 @@ export class Repo extends EventEmitter {
29
31
  /** By default, we share generously with all peers. */
30
32
  /** @hidden */
31
33
  sharePolicy = async () => true;
32
- constructor({ storage, network, peerId, sharePolicy }) {
34
+ /** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
35
+ /** @hidden */
36
+ peerMetadataByPeerId = {};
37
+ #remoteHeadsSubscriptions = new RemoteHeadsSubscriptions();
38
+ #remoteHeadsGossipingEnabled = false;
39
+ constructor({ storage, network, peerId, sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, }) {
33
40
  super();
41
+ this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
34
42
  this.#log = debug(`automerge-repo:repo`);
35
43
  this.sharePolicy = sharePolicy ?? this.sharePolicy;
36
44
  // DOC COLLECTION
@@ -42,7 +50,7 @@ export class Repo extends EventEmitter {
42
50
  const saveFn = ({ handle, doc, }) => {
43
51
  void storageSubsystem.saveDoc(handle.documentId, doc);
44
52
  };
45
- const debouncedSaveFn = handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
53
+ handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
46
54
  if (isNew) {
47
55
  // this is a new document, immediately save it
48
56
  await storageSubsystem.saveDoc(handle.documentId, handle.docSync());
@@ -95,33 +103,137 @@ export class Repo extends EventEmitter {
95
103
  this.#log(`sending ${message.type} message to ${message.targetId}`);
96
104
  networkSubsystem.send(message);
97
105
  });
106
+ if (this.#remoteHeadsGossipingEnabled) {
107
+ this.#synchronizer.on("open-doc", ({ peerId, documentId }) => {
108
+ this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
109
+ });
110
+ }
98
111
  // STORAGE
99
112
  // The storage subsystem has access to some form of persistence, and deals with save and loading documents.
100
113
  const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
101
114
  this.storageSubsystem = storageSubsystem;
102
115
  // NETWORK
103
116
  // The network subsystem deals with sending and receiving messages to and from peers.
104
- const networkSubsystem = new NetworkSubsystem(network, peerId);
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);
105
124
  this.networkSubsystem = networkSubsystem;
106
125
  // When we get a new peer, register it with the synchronizer
107
- networkSubsystem.on("peer", async ({ peerId }) => {
126
+ networkSubsystem.on("peer", async ({ peerId, peerMetadata }) => {
108
127
  this.#log("peer connected", { peerId });
128
+ if (peerMetadata) {
129
+ this.peerMetadataByPeerId[peerId] = { ...peerMetadata };
130
+ }
131
+ this.sharePolicy(peerId)
132
+ .then(shouldShare => {
133
+ if (shouldShare && this.#remoteHeadsGossipingEnabled) {
134
+ this.#remoteHeadsSubscriptions.addGenerousPeer(peerId);
135
+ }
136
+ })
137
+ .catch(err => {
138
+ console.log("error in share policy", { err });
139
+ });
109
140
  this.#synchronizer.addPeer(peerId);
110
141
  });
111
142
  // When a peer disconnects, remove it from the synchronizer
112
143
  networkSubsystem.on("peer-disconnected", ({ peerId }) => {
113
144
  this.#synchronizer.removePeer(peerId);
145
+ this.#remoteHeadsSubscriptions.removePeer(peerId);
114
146
  });
115
147
  // Handle incoming messages
116
148
  networkSubsystem.on("message", async (msg) => {
117
- await this.#synchronizer.receiveMessage(msg);
149
+ this.#receiveMessage(msg);
118
150
  });
119
- if (storageSubsystem) {
120
- const debouncedSaveSyncState = throttle(({ documentId, peerId, syncState }) => {
121
- storageSubsystem.saveSyncState(documentId, peerId, syncState);
151
+ this.#synchronizer.on("sync-state", message => {
152
+ this.#saveSyncState(message);
153
+ const handle = this.#handleCache[message.documentId];
154
+ const { storageId } = this.peerMetadataByPeerId[message.peerId] || {};
155
+ if (!storageId) {
156
+ return;
157
+ }
158
+ const heads = handle.getRemoteHeads(storageId);
159
+ const haveHeadsChanged = message.syncState.theirHeads &&
160
+ (!heads || !headsAreSame(heads, message.syncState.theirHeads));
161
+ if (haveHeadsChanged) {
162
+ handle.setRemoteHeads(storageId, message.syncState.theirHeads);
163
+ if (storageId && this.#remoteHeadsGossipingEnabled) {
164
+ this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(message.documentId, storageId, message.syncState.theirHeads);
165
+ }
166
+ }
167
+ });
168
+ if (this.#remoteHeadsGossipingEnabled) {
169
+ this.#remoteHeadsSubscriptions.on("notify-remote-heads", message => {
170
+ this.networkSubsystem.send({
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
+ });
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
+ }
198
+ }
199
+ #receiveMessage(message) {
200
+ switch (message.type) {
201
+ case "remote-subscription-change":
202
+ if (this.#remoteHeadsGossipingEnabled) {
203
+ this.#remoteHeadsSubscriptions.handleControlMessage(message);
204
+ }
205
+ break;
206
+ case "remote-heads-changed":
207
+ if (this.#remoteHeadsGossipingEnabled) {
208
+ this.#remoteHeadsSubscriptions.handleRemoteHeads(message);
209
+ }
210
+ break;
211
+ case "sync":
212
+ case "request":
213
+ case "ephemeral":
214
+ case "doc-unavailable":
215
+ this.#synchronizer.receiveMessage(message).catch(err => {
216
+ console.log("error receiving message", { err });
217
+ });
218
+ }
219
+ }
220
+ #throttledSaveSyncStateHandlers = {};
221
+ /** saves sync state throttled per storage id, if a peer doesn't have a storage id it's sync state is not persisted */
222
+ #saveSyncState(payload) {
223
+ if (!this.storageSubsystem) {
224
+ return;
225
+ }
226
+ const { storageId, isEphemeral } = this.peerMetadataByPeerId[payload.peerId] || {};
227
+ if (!storageId || isEphemeral) {
228
+ return;
229
+ }
230
+ let handler = this.#throttledSaveSyncStateHandlers[storageId];
231
+ if (!handler) {
232
+ handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(({ documentId, syncState }) => {
233
+ void this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
122
234
  }, this.saveDebounceRate);
123
- this.#synchronizer.on("sync-state", debouncedSaveSyncState);
124
235
  }
236
+ handler(payload);
125
237
  }
126
238
  /** Returns an existing handle if we have it; creates one otherwise. */
127
239
  #getHandle(
@@ -147,6 +259,9 @@ export class Repo extends EventEmitter {
147
259
  get peers() {
148
260
  return this.#synchronizer.peers;
149
261
  }
262
+ getStorageIdOfPeer(peerId) {
263
+ return this.peerMetadataByPeerId[peerId]?.storageId;
264
+ }
150
265
  /**
151
266
  * Creates a new document and returns a handle to it. The initial value of the document is
152
267
  * an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
@@ -237,4 +352,48 @@ export class Repo extends EventEmitter {
237
352
  delete this.#handleCache[documentId];
238
353
  this.emit("delete-document", { documentId });
239
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
+ }
382
+ 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
+ }
390
+ };
391
+ storageId = async () => {
392
+ if (!this.storageSubsystem) {
393
+ return undefined;
394
+ }
395
+ else {
396
+ return this.storageSubsystem.id();
397
+ }
398
+ };
240
399
  }
@@ -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 */
package/dist/index.d.ts CHANGED
@@ -29,14 +29,14 @@ export { DocHandle } from "./DocHandle.js";
29
29
  export { isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "./AutomergeUrl.js";
30
30
  export { Repo } from "./Repo.js";
31
31
  export { NetworkAdapter } from "./network/NetworkAdapter.js";
32
- export { isValidRepoMessage } from "./network/messages.js";
32
+ export { isRepoMessage } from "./network/messages.js";
33
33
  export { StorageAdapter } from "./storage/StorageAdapter.js";
34
34
  /** @hidden **/
35
35
  export * as cbor from "./helpers/cbor.js";
36
36
  export type { DocHandleChangePayload, DocHandleDeletePayload, DocHandleEncodedChangePayload, DocHandleEphemeralMessagePayload, DocHandleRemoteHeadsPayload, DocHandleEvents, DocHandleOptions, DocHandleOutboundEphemeralMessagePayload, HandleState, } from "./DocHandle.js";
37
37
  export type { DeleteDocumentPayload, DocumentPayload, RepoConfig, RepoEvents, SharePolicy, } from "./Repo.js";
38
- export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, } from "./network/NetworkAdapter.js";
38
+ export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, PeerMetadata, } from "./network/NetworkAdapter.js";
39
39
  export type { DocumentUnavailableMessage, EphemeralMessage, Message, RepoMessage, RequestMessage, SyncMessage, } from "./network/messages.js";
40
- export type { Chunk, ChunkInfo, ChunkType, StorageKey, } from "./storage/types.js";
40
+ export type { Chunk, ChunkInfo, ChunkType, StorageKey, StorageId, } from "./storage/types.js";
41
41
  export * from "./types.js";
42
42
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAE5D,eAAe;AACf,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA;AAIzC,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,wCAAwC,EACxC,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EACV,0BAA0B,EAC1B,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,YAAY,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,oBAAoB,CAAA;AAE3B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAE5D,eAAe;AACf,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA;AAIzC,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,wCAAwC,EACxC,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,uBAAuB,EACvB,YAAY,GACb,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EACV,0BAA0B,EAC1B,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,YAAY,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAA;AAE3B,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ export { DocHandle } from "./DocHandle.js";
29
29
  export { isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "./AutomergeUrl.js";
30
30
  export { Repo } from "./Repo.js";
31
31
  export { NetworkAdapter } from "./network/NetworkAdapter.js";
32
- export { isValidRepoMessage } from "./network/messages.js";
32
+ export { isRepoMessage } from "./network/messages.js";
33
33
  export { StorageAdapter } from "./storage/StorageAdapter.js";
34
34
  /** @hidden **/
35
35
  export * as cbor from "./helpers/cbor.js";
@@ -1,6 +1,17 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
2
  import { PeerId } from "../types.js";
3
3
  import { Message } from "./messages.js";
4
+ import { StorageId } from "../storage/types.js";
5
+ /**
6
+ * Describes a peer intent to the system
7
+ * storageId: the key for syncState to decide what the other peer already has
8
+ * isEphemeral: to decide if we bother recording this peer's sync state
9
+ *
10
+ */
11
+ export interface PeerMetadata {
12
+ storageId?: StorageId;
13
+ isEphemeral?: boolean;
14
+ }
4
15
  /** An interface representing some way to connect to other peers
5
16
  *
6
17
  * @remarks
@@ -10,11 +21,13 @@ import { Message } from "./messages.js";
10
21
  */
11
22
  export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
12
23
  peerId?: PeerId;
24
+ peerMetadata?: PeerMetadata;
13
25
  /** Called by the {@link Repo} to start the connection process
14
26
  *
15
27
  * @argument peerId - the peerId of this repo
28
+ * @argument peerMetadata - how this adapter should present itself to other peers
16
29
  */
17
- abstract connect(peerId: PeerId): void;
30
+ abstract connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
18
31
  /** Called by the {@link Repo} to send a message to a peer
19
32
  *
20
33
  * @argument message - the message to send
@@ -40,6 +53,7 @@ export interface OpenPayload {
40
53
  }
41
54
  export interface PeerCandidatePayload {
42
55
  peerId: PeerId;
56
+ peerMetadata: PeerMetadata;
43
57
  }
44
58
  export interface PeerDisconnectedPayload {
45
59
  peerId: PeerId;
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC;;;;;;GAMG;AACH,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAEtC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,IAAI,IAAI;CAC5B;AAID,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAErC,yCAAyC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAA;IAEjB,+DAA+D;IAC/D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAEzD,2EAA2E;IAC3E,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAE/D,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
1
+ {"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;GAMG;AACH,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,YAAY,CAAA;IAE3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI;IAEnE;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,IAAI,IAAI;CAC5B;AAID,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAErC,yCAAyC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAA;IAEjB,+DAA+D;IAC/D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAEzD,2EAA2E;IAC3E,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAE/D,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -1,3 +1,4 @@
1
+ /* c8 ignore start */
1
2
  import { EventEmitter } from "eventemitter3";
2
3
  /** An interface representing some way to connect to other peers
3
4
  *
@@ -7,5 +8,6 @@ import { EventEmitter } from "eventemitter3";
7
8
  * until the adapter emits a `ready` event before it starts trying to use it
8
9
  */
9
10
  export class NetworkAdapter extends EventEmitter {
10
- peerId; // hmmm, maybe not
11
+ peerId;
12
+ peerMetadata;
11
13
  }
@@ -1,11 +1,12 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
2
  import { PeerId } from "../types.js";
3
- import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
3
+ import type { NetworkAdapter, PeerDisconnectedPayload, PeerMetadata } from "./NetworkAdapter.js";
4
4
  import { MessageContents, RepoMessage } from "./messages.js";
5
5
  export declare class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
6
6
  #private;
7
7
  peerId: PeerId;
8
- constructor(adapters: NetworkAdapter[], peerId?: PeerId);
8
+ private peerMetadata;
9
+ constructor(adapters: NetworkAdapter[], peerId: PeerId, peerMetadata: Promise<PeerMetadata>);
9
10
  addNetworkAdapter(networkAdapter: NetworkAdapter): void;
10
11
  send(message: MessageContents): void;
11
12
  isReady: () => boolean;
@@ -19,5 +20,6 @@ export interface NetworkSubsystemEvents {
19
20
  }
20
21
  export interface PeerPayload {
21
22
  peerId: PeerId;
23
+ peerMetadata: PeerMetadata;
22
24
  }
23
25
  //# sourceMappingURL=NetworkSubsystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAUzB,MAAM;gBAAzC,QAAQ,EAAE,cAAc,EAAE,EAAS,MAAM,SAAiB;IAMtE,iBAAiB,CAAC,cAAc,EAAE,cAAc;IAsEhD,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,sBAUR;CACF;AAQD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;CACf"}
1
+ {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAY/D,MAAM;IACb,OAAO,CAAC,YAAY;gBAFpB,QAAQ,EAAE,cAAc,EAAE,EACnB,MAAM,QAAiB,EACtB,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;IAO7C,iBAAiB,CAAC,cAAc,EAAE,cAAc;IAyEhD,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,sBAUR;CACF;AAQD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B"}
@@ -1,9 +1,10 @@
1
1
  import debug from "debug";
2
2
  import { EventEmitter } from "eventemitter3";
3
- import { isEphemeralMessage, isValidRepoMessage, } from "./messages.js";
3
+ import { isEphemeralMessage, isRepoMessage, } from "./messages.js";
4
4
  const getEphemeralMessageSource = (message) => `${message.senderId}:${message.sessionId}`;
5
5
  export class NetworkSubsystem extends EventEmitter {
6
6
  peerId;
7
+ peerMetadata;
7
8
  #log;
8
9
  #adaptersByPeer = {};
9
10
  #count = 0;
@@ -11,9 +12,10 @@ export class NetworkSubsystem extends EventEmitter {
11
12
  #ephemeralSessionCounts = {};
12
13
  #readyAdapterCount = 0;
13
14
  #adapters = [];
14
- constructor(adapters, peerId = randomPeerId()) {
15
+ constructor(adapters, peerId = randomPeerId(), peerMetadata) {
15
16
  super();
16
17
  this.peerId = peerId;
18
+ this.peerMetadata = peerMetadata;
17
19
  this.#log = debug(`automerge-repo:network:${this.peerId}`);
18
20
  adapters.forEach(a => this.addNetworkAdapter(a));
19
21
  }
@@ -26,14 +28,14 @@ export class NetworkSubsystem extends EventEmitter {
26
28
  this.emit("ready");
27
29
  }
28
30
  });
29
- networkAdapter.on("peer-candidate", ({ peerId }) => {
31
+ networkAdapter.on("peer-candidate", ({ peerId, peerMetadata }) => {
30
32
  this.#log(`peer candidate: ${peerId} `);
31
33
  // TODO: This is where authentication would happen
32
34
  if (!this.#adaptersByPeer[peerId]) {
33
35
  // TODO: handle losing a server here
34
36
  this.#adaptersByPeer[peerId] = networkAdapter;
35
37
  }
36
- this.emit("peer", { peerId });
38
+ this.emit("peer", { peerId, peerMetadata });
37
39
  });
38
40
  networkAdapter.on("peer-disconnected", ({ peerId }) => {
39
41
  this.#log(`peer disconnected: ${peerId} `);
@@ -41,7 +43,7 @@ export class NetworkSubsystem extends EventEmitter {
41
43
  this.emit("peer-disconnected", { peerId });
42
44
  });
43
45
  networkAdapter.on("message", msg => {
44
- if (!isValidRepoMessage(msg)) {
46
+ if (!isRepoMessage(msg)) {
45
47
  this.#log(`invalid message: ${JSON.stringify(msg)}`);
46
48
  return;
47
49
  }
@@ -65,7 +67,11 @@ export class NetworkSubsystem extends EventEmitter {
65
67
  }
66
68
  });
67
69
  });
68
- networkAdapter.connect(this.peerId);
70
+ this.peerMetadata.then(peerMetadata => {
71
+ networkAdapter.connect(this.peerId, peerMetadata);
72
+ }).catch(err => {
73
+ this.#log("error connecting to network", err);
74
+ });
69
75
  }
70
76
  send(message) {
71
77
  const peer = this.#adaptersByPeer[message.targetId];
@@ -101,7 +107,7 @@ export class NetworkSubsystem extends EventEmitter {
101
107
  }
102
108
  };
103
109
  const outbound = prepareMessage(message);
104
- this.#log("sending message", outbound);
110
+ this.#log("sending message %o", outbound);
105
111
  peer.send(outbound);
106
112
  }
107
113
  isReady = () => {