@automerge/automerge-repo 1.1.0-alpha.6 → 1.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,6 +45,8 @@ 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 `Automerge.save(doc)`) into the repo, returning a new handle
48
50
  - `.on("document", ({handle: DocHandle}) => void)`
49
51
  Registers a callback to be fired each time a new document is loaded or created.
50
52
  - `.on("delete-document", ({handle: DocHandle}) => void)`
@@ -64,7 +66,7 @@ the document.
64
66
 
65
67
  A `DocHandle` also emits these events:
66
68
 
67
- - `change({handle: DocHandle, patches: Patch[], patchInfo: PatchInfo})`
69
+ - `change({handle: DocHandle, patches: Patch[], patchInfo: PatchInfo})`
68
70
  Called whenever the document changes, the handle's .doc
69
71
  - `delete`
70
72
  Called when the document is deleted locally.
@@ -85,11 +87,12 @@ network adapter:
85
87
  const repo = new Repo({
86
88
  network: [new BroadcastChannelNetworkAdapter()],
87
89
  storage: new IndexedDBStorageAdapter(),
88
- sharePolicy: async (peerId: PeerId, documentId: DocumentId) => true // this is the default
90
+ sharePolicy: async (peerId: PeerId, documentId: DocumentId) => true, // this is the default
89
91
  })
90
92
  ```
91
93
 
92
94
  ### Share Policy
95
+
93
96
  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
97
 
95
98
  > **Warning**
@@ -99,7 +102,6 @@ You can override this by providing a custom share policy. The function should re
99
102
 
100
103
  The share policy will not stop a document being _requested_ by another peer by its `DocumentId`.
101
104
 
102
- ```ts
103
105
  ## Starting the demo app
104
106
 
105
107
  ```bash
@@ -272,7 +274,8 @@ you'll need to manually copy the `rootDocId` value between the browsers.)
272
274
  Originally authored by Peter van Hardenberg.
273
275
 
274
276
  With gratitude for contributions by:
275
- - Herb Caudill
276
- - Jeremy Rose
277
- - Alex Currie-Clark
278
- - Dylan Mackenzie
277
+
278
+ - Herb Caudill
279
+ - Jeremy Rose
280
+ - Alex Currie-Clark
281
+ - Dylan Mackenzie
@@ -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
@@ -248,7 +248,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
248
248
  }
249
249
  }
250
250
  #isPeerSubscribedToDoc(peerId, documentId) {
251
- let subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
251
+ const subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
252
252
  return subscribedDocs && subscribedDocs.has(documentId);
253
253
  }
254
254
  /** Returns the (document, storageId) pairs which have changed after processing msg */
@@ -262,11 +262,11 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
262
262
  }
263
263
  let remote = this.#knownHeads.get(documentId);
264
264
  if (!remote) {
265
- remote = new Map([[storageId, { heads, timestamp }]]);
265
+ remote = new Map();
266
266
  this.#knownHeads.set(documentId, remote);
267
267
  }
268
268
  const docRemote = remote.get(storageId);
269
- if (docRemote && docRemote.timestamp > timestamp) {
269
+ if (docRemote && docRemote.timestamp >= timestamp) {
270
270
  continue;
271
271
  }
272
272
  else {
package/dist/Repo.d.ts CHANGED
@@ -67,6 +67,11 @@ export declare class Repo extends EventEmitter<RepoEvents> {
67
67
  delete(
68
68
  /** The url or documentId of the handle to delete */
69
69
  id: AnyDocumentId): void;
70
+ /**
71
+ * Imports document binary into the repo.
72
+ * @param binary - The binary to import
73
+ */
74
+ import<T>(binary: Uint8Array): DocHandle<T>;
70
75
  subscribeToRemotes: (remotes: StorageId[]) => void;
71
76
  storageId: () => Promise<StorageId | undefined>;
72
77
  }
@@ -1 +1 @@
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;AAEzE,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAI9C,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;gBAI3C,EACV,OAAO,EACP,OAAO,EACP,MAAM,EACN,WAAW,EACX,WAAmC,GACpC,EAAE,UAAU;IA2Qb,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,kBAAkB,YAAa,SAAS,EAAE,UAGzC;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;CAC1B;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"}
1
+ {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
package/dist/Repo.js CHANGED
@@ -48,7 +48,7 @@ export class Repo extends EventEmitter {
48
48
  const saveFn = ({ handle, doc, }) => {
49
49
  void storageSubsystem.saveDoc(handle.documentId, doc);
50
50
  };
51
- const debouncedSaveFn = handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
51
+ handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
52
52
  if (isNew) {
53
53
  // this is a new document, immediately save it
54
54
  await storageSubsystem.saveDoc(handle.documentId, handle.docSync());
@@ -110,7 +110,9 @@ export class Repo extends EventEmitter {
110
110
  this.storageSubsystem = storageSubsystem;
111
111
  // NETWORK
112
112
  // The network subsystem deals with sending and receiving messages to and from peers.
113
- const myPeerMetadata = new Promise(async (resolve, reject) => resolve({
113
+ const myPeerMetadata = new Promise(
114
+ // eslint-disable-next-line no-async-promise-executor -- TODO: fix
115
+ async (resolve) => resolve({
114
116
  storageId: await storageSubsystem?.id(),
115
117
  isEphemeral,
116
118
  }));
@@ -218,7 +220,10 @@ export class Repo extends EventEmitter {
218
220
  let handler = this.#throttledSaveSyncStateHandlers[storageId];
219
221
  if (!handler) {
220
222
  handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(({ documentId, syncState }) => {
221
- this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
223
+ this.storageSubsystem.saveSyncState(documentId, storageId, syncState)
224
+ .catch(err => {
225
+ this.#log("error saving sync state", { err });
226
+ });
222
227
  }, this.saveDebounceRate);
223
228
  }
224
229
  handler(message);
@@ -340,6 +345,18 @@ export class Repo extends EventEmitter {
340
345
  delete this.#handleCache[documentId];
341
346
  this.emit("delete-document", { documentId });
342
347
  }
348
+ /**
349
+ * Imports document binary into the repo.
350
+ * @param binary - The binary to import
351
+ */
352
+ import(binary) {
353
+ const doc = Automerge.load(binary);
354
+ const handle = this.create();
355
+ handle.update(() => {
356
+ return Automerge.clone(doc);
357
+ });
358
+ return handle;
359
+ }
343
360
  subscribeToRemotes = (remotes) => {
344
361
  this.#log("subscribeToRemotes", { remotes });
345
362
  this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes);
@@ -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
  };
@@ -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
  };
@@ -7,6 +7,6 @@ import { EventEmitter } from "eventemitter3";
7
7
  * until the adapter emits a `ready` event before it starts trying to use it
8
8
  */
9
9
  export class NetworkAdapter extends EventEmitter {
10
- peerId; // hmmm, maybe not
10
+ peerId;
11
11
  peerMetadata;
12
12
  }
@@ -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,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAQtB,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;IAuEhD,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
+ {"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"}
@@ -69,6 +69,8 @@ export class NetworkSubsystem extends EventEmitter {
69
69
  });
70
70
  this.peerMetadata.then(peerMetadata => {
71
71
  networkAdapter.connect(this.peerId, peerMetadata);
72
+ }).catch(err => {
73
+ this.#log("error connecting to network", err);
72
74
  });
73
75
  }
74
76
  send(message) {
@@ -23,7 +23,7 @@ export class StorageSubsystem {
23
23
  this.#storageAdapter = storageAdapter;
24
24
  }
25
25
  async id() {
26
- let storedId = await this.#storageAdapter.load(["storage-adapter-id"]);
26
+ const storedId = await this.#storageAdapter.load(["storage-adapter-id"]);
27
27
  let id;
28
28
  if (storedId) {
29
29
  id = new TextDecoder().decode(storedId);
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAe,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAqD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAYlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
1
+ {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAqD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAYlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;CAC9D;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IA8HD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA+C3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
1
+ {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;CAC9D;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAgID,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAiD3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
@@ -79,6 +79,8 @@ export class DocSynchronizer extends Synchronizer {
79
79
  if (!pendingCallbacks) {
80
80
  this.#onLoadSyncState(peerId).then(syncState => {
81
81
  this.#initSyncState(peerId, syncState ?? A.initSyncState());
82
+ }).catch(err => {
83
+ this.#log(`Error loading sync state for ${peerId}: ${err}`);
82
84
  });
83
85
  pendingCallbacks = this.#pendingSyncStateCallbacks[peerId] = [];
84
86
  }
@@ -178,6 +180,8 @@ export class DocSynchronizer extends Synchronizer {
178
180
  if (doc) {
179
181
  this.#sendSyncMessage(peerId, doc);
180
182
  }
183
+ }).catch(err => {
184
+ this.#log(`Error loading doc for ${peerId}: ${err}`);
181
185
  });
182
186
  });
183
187
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo",
3
- "version": "1.1.0-alpha.6",
3
+ "version": "1.1.0-alpha.7",
4
4
  "description": "A repository object to manage a collection of automerge documents",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -9,6 +9,7 @@
9
9
  "main": "dist/index.js",
10
10
  "scripts": {
11
11
  "build": "tsc",
12
+ "lint": "eslint --ext .ts src",
12
13
  "watch": "npm-watch build",
13
14
  "test:coverage": "c8 --reporter=lcov --reporter=html --reporter=text yarn test",
14
15
  "test": "vitest",
@@ -20,7 +21,7 @@
20
21
  },
21
22
  "devDependencies": {
22
23
  "http-server": "^14.1.0",
23
- "vite": "^4.4.11"
24
+ "vite": "^5.0.8"
24
25
  },
25
26
  "dependencies": {
26
27
  "@automerge/automerge": "^2.1.9",
@@ -55,5 +56,5 @@
55
56
  "publishConfig": {
56
57
  "access": "public"
57
58
  },
58
- "gitHead": "77ad0475a3b241a8efdf4e282fdffc8fed09b101"
59
+ "gitHead": "9a4711e39c93273d992c5686257246ddfaaafddd"
59
60
  }
@@ -33,7 +33,7 @@ export const parseAutomergeUrl = (url: AutomergeUrl) => {
33
33
  export const stringifyAutomergeUrl = (
34
34
  arg: UrlOptions | DocumentId | BinaryDocumentId
35
35
  ) => {
36
- let documentId =
36
+ const documentId =
37
37
  arg instanceof Uint8Array || typeof arg === "string"
38
38
  ? arg
39
39
  : "documentId" in arg
@@ -326,7 +326,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
326
326
  }
327
327
 
328
328
  #isPeerSubscribedToDoc(peerId: PeerId, documentId: DocumentId) {
329
- let subscribedDocs = this.#subscribedDocsByPeer.get(peerId)
329
+ const subscribedDocs = this.#subscribedDocsByPeer.get(peerId)
330
330
  return subscribedDocs && subscribedDocs.has(documentId)
331
331
  }
332
332
 
@@ -348,12 +348,12 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
348
348
  }
349
349
  let remote = this.#knownHeads.get(documentId)
350
350
  if (!remote) {
351
- remote = new Map([[storageId as StorageId, { heads, timestamp }]])
351
+ remote = new Map()
352
352
  this.#knownHeads.set(documentId, remote)
353
353
  }
354
354
 
355
355
  const docRemote = remote.get(storageId as StorageId)
356
- if (docRemote && docRemote.timestamp > timestamp) {
356
+ if (docRemote && docRemote.timestamp >= timestamp) {
357
357
  continue
358
358
  } else {
359
359
  remote.set(storageId as StorageId, { timestamp, heads })
package/src/Repo.ts CHANGED
@@ -77,7 +77,7 @@ export class Repo extends EventEmitter<RepoEvents> {
77
77
  }: DocHandleEncodedChangePayload<any>) => {
78
78
  void storageSubsystem.saveDoc(handle.documentId, doc)
79
79
  }
80
- const debouncedSaveFn = handle.on(
80
+ handle.on(
81
81
  "heads-changed",
82
82
  throttle(saveFn, this.saveDebounceRate)
83
83
  )
@@ -153,7 +153,8 @@ export class Repo extends EventEmitter<RepoEvents> {
153
153
  // The network subsystem deals with sending and receiving messages to and from peers.
154
154
 
155
155
  const myPeerMetadata: Promise<PeerMetadata> = new Promise(
156
- async (resolve, reject) =>
156
+ // eslint-disable-next-line no-async-promise-executor -- TODO: fix
157
+ async (resolve) =>
157
158
  resolve({
158
159
  storageId: await storageSubsystem?.id(),
159
160
  isEphemeral,
@@ -300,6 +301,9 @@ export class Repo extends EventEmitter<RepoEvents> {
300
301
  handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(
301
302
  ({ documentId, syncState }: SyncStateMessage) => {
302
303
  this.storageSubsystem!.saveSyncState(documentId, storageId, syncState)
304
+ .catch(err => {
305
+ this.#log("error saving sync state", { err })
306
+ })
303
307
  },
304
308
  this.saveDebounceRate
305
309
  )
@@ -450,6 +454,22 @@ export class Repo extends EventEmitter<RepoEvents> {
450
454
  this.emit("delete-document", { documentId })
451
455
  }
452
456
 
457
+ /**
458
+ * Imports document binary into the repo.
459
+ * @param binary - The binary to import
460
+ */
461
+ import<T>(binary: Uint8Array) {
462
+ const doc = Automerge.load<T>(binary)
463
+
464
+ const handle = this.create<T>()
465
+
466
+ handle.update(() => {
467
+ return Automerge.clone(doc)
468
+ })
469
+
470
+ return handle
471
+ }
472
+
453
473
  subscribeToRemotes = (remotes: StorageId[]) => {
454
474
  this.#log("subscribeToRemotes", { remotes })
455
475
  this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes)
@@ -19,7 +19,7 @@ export const throttle = <F extends (...args: Parameters<F>) => ReturnType<F>>(
19
19
  return function (...args: Parameters<F>) {
20
20
  clearTimeout(timeout)
21
21
  timeout = setTimeout(() => {
22
- fn.apply(null, args)
22
+ fn(...args)
23
23
  }, rate)
24
24
  }
25
25
  }
@@ -36,7 +36,7 @@ export const throttle = <F extends (...args: Parameters<F>) => ReturnType<F>>(
36
36
  wait = lastCall + delay - Date.now()
37
37
  clearTimeout(timeout)
38
38
  timeout = setTimeout(() => {
39
- fn.apply(null, args)
39
+ fn(...args)
40
40
  lastCall = Date.now()
41
41
  }, wait)
42
42
  }
@@ -22,7 +22,7 @@ export interface PeerMetadata {
22
22
  * until the adapter emits a `ready` event before it starts trying to use it
23
23
  */
24
24
  export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
25
- peerId?: PeerId // hmmm, maybe not
25
+ peerId?: PeerId
26
26
  peerMetadata?: PeerMetadata
27
27
 
28
28
  /** Called by the {@link Repo} to start the connection process
@@ -13,7 +13,6 @@ import {
13
13
  isEphemeralMessage,
14
14
  isValidRepoMessage,
15
15
  } from "./messages.js"
16
- import { StorageId } from "../storage/types.js"
17
16
 
18
17
  type EphemeralMessageSource = `${PeerId}:${SessionId}`
19
18
 
@@ -108,6 +107,8 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
108
107
 
109
108
  this.peerMetadata.then(peerMetadata => {
110
109
  networkAdapter.connect(this.peerId, peerMetadata)
110
+ }).catch(err => {
111
+ this.#log("error connecting to network", err)
111
112
  })
112
113
  }
113
114
 
@@ -33,7 +33,7 @@ export class StorageSubsystem {
33
33
  }
34
34
 
35
35
  async id(): Promise<StorageId> {
36
- let storedId = await this.#storageAdapter.load(["storage-adapter-id"])
36
+ const storedId = await this.#storageAdapter.load(["storage-adapter-id"])
37
37
 
38
38
  let id: StorageId
39
39
  if (storedId) {
@@ -2,7 +2,7 @@ import debug from "debug"
2
2
  import { DocHandle } from "../DocHandle.js"
3
3
  import { stringifyAutomergeUrl } from "../AutomergeUrl.js"
4
4
  import { Repo } from "../Repo.js"
5
- import { DocMessage, RepoMessage } from "../network/messages.js"
5
+ import { DocMessage } from "../network/messages.js"
6
6
  import { DocumentId, PeerId } from "../types.js"
7
7
  import { DocSynchronizer } from "./DocSynchronizer.js"
8
8
  import { Synchronizer } from "./Synchronizer.js"
@@ -139,6 +139,8 @@ export class DocSynchronizer extends Synchronizer {
139
139
  if (!pendingCallbacks) {
140
140
  this.#onLoadSyncState(peerId).then(syncState => {
141
141
  this.#initSyncState(peerId, syncState ?? A.initSyncState())
142
+ }).catch(err => {
143
+ this.#log(`Error loading sync state for ${peerId}: ${err}`)
142
144
  })
143
145
  pendingCallbacks = this.#pendingSyncStateCallbacks[peerId] = []
144
146
  }
@@ -264,6 +266,8 @@ export class DocSynchronizer extends Synchronizer {
264
266
  if (doc) {
265
267
  this.#sendSyncMessage(peerId, doc)
266
268
  }
269
+ }).catch(err => {
270
+ this.#log(`Error loading doc for ${peerId}: ${err}`)
267
271
  })
268
272
  })
269
273
  })
@@ -287,7 +287,7 @@ describe("RepoHeadsSubscriptions", () => {
287
287
  assert.strictEqual(messages.length, 0)
288
288
  })
289
289
 
290
- it("should ignore sync states with an older timestamp", async () => {
290
+ it("should only notify of sync states with a more recent timestamp", async () => {
291
291
  const remoteHeadsSubscription = new RemoteHeadsSubscriptions()
292
292
 
293
293
  const messagesPromise = waitForMessages(
@@ -298,6 +298,9 @@ describe("RepoHeadsSubscriptions", () => {
298
298
  remoteHeadsSubscription.subscribeToRemotes([storageB])
299
299
  remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
300
300
 
301
+ // send same message
302
+ remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
303
+
301
304
  // send message with old heads
302
305
  remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB)
303
306
 
package/test/Repo.test.ts CHANGED
@@ -2,7 +2,7 @@ import { next as A } from "@automerge/automerge"
2
2
  import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
3
3
  import assert from "assert"
4
4
  import * as Uuid from "uuid"
5
- import { describe, it } from "vitest"
5
+ import { describe, expect, it } from "vitest"
6
6
  import { READY } from "../src/DocHandle.js"
7
7
  import { parseAutomergeUrl } from "../src/AutomergeUrl.js"
8
8
  import {
@@ -396,6 +396,30 @@ describe("Repo", () => {
396
396
  const storageKeyTypes = storageAdapter.keys().map(k => k.split(".")[1])
397
397
  assert(storageKeyTypes.filter(k => k === "snapshot").length === 1)
398
398
  })
399
+
400
+ it("can import an existing document", async () => {
401
+ const { repo } = setup()
402
+ const doc = A.init<TestDoc>()
403
+ const updatedDoc = A.change(doc, d => {
404
+ d.foo = "bar"
405
+ })
406
+
407
+ const saved = A.save(updatedDoc)
408
+
409
+ const handle = repo.import<TestDoc>(saved)
410
+ assert.equal(handle.isReady(), true)
411
+ const v = await handle.doc()
412
+ assert.equal(v?.foo, "bar")
413
+
414
+ expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
415
+ })
416
+
417
+ it("throws an error if we try to import an invalid document", async () => {
418
+ const { repo } = setup()
419
+ expect(() => {
420
+ repo.import<TestDoc>(A.init<TestDoc> as unknown as Uint8Array)
421
+ }).toThrow()
422
+ })
399
423
  })
400
424
 
401
425
  describe("with peers (linear network)", async () => {
@@ -165,11 +165,9 @@ describe("DocHandle.remoteHeads", () => {
165
165
  ).filter(({ type }) => type === "remote-heads-changed")
166
166
 
167
167
  // we should only be notified of the head changes of doc A
168
- assert.strictEqual(remoteHeadsChangedMessages.length, 1)
169
- assert.strictEqual(
170
- remoteHeadsChangedMessages[0].documentId,
171
- leftTabDocA.documentId
172
- )
168
+ const docIds = remoteHeadsChangedMessages.map(d => d.documentId)
169
+ const uniqueDocIds = [...new Set(docIds)]
170
+ assert.deepStrictEqual(uniqueDocIds, [leftTabDocA.documentId])
173
171
  })
174
172
 
175
173
  it("should report remote heads for doc on subscribe if peer already knows them", async () => {