@automerge/automerge-repo 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/DocHandle.d.ts +7 -7
  3. package/dist/DocHandle.d.ts.map +1 -1
  4. package/dist/DocHandle.js +3 -7
  5. package/dist/EphemeralData.d.ts +2 -2
  6. package/dist/EphemeralData.d.ts.map +1 -1
  7. package/dist/Repo.d.ts.map +1 -1
  8. package/dist/Repo.js +7 -11
  9. package/dist/helpers/cbor.d.ts +2 -2
  10. package/dist/helpers/cbor.d.ts.map +1 -1
  11. package/dist/helpers/cbor.js +1 -1
  12. package/dist/helpers/pause.d.ts.map +1 -1
  13. package/dist/helpers/pause.js +3 -1
  14. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  15. package/dist/helpers/tests/network-adapter-tests.js +2 -2
  16. package/dist/index.d.ts +11 -9
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -4
  19. package/dist/network/NetworkAdapter.d.ts +3 -3
  20. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  21. package/dist/network/NetworkSubsystem.d.ts +2 -2
  22. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  23. package/dist/network/NetworkSubsystem.js +30 -18
  24. package/dist/network/messages.d.ts +38 -68
  25. package/dist/network/messages.d.ts.map +1 -1
  26. package/dist/network/messages.js +13 -21
  27. package/dist/storage/StorageSubsystem.js +7 -7
  28. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -3
  29. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  30. package/dist/synchronizer/CollectionSynchronizer.js +2 -2
  31. package/dist/synchronizer/DocSynchronizer.d.ts +3 -3
  32. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  33. package/dist/synchronizer/DocSynchronizer.js +22 -29
  34. package/dist/synchronizer/Synchronizer.d.ts +2 -2
  35. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  36. package/dist/types.d.ts +5 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +5 -13
  39. package/src/DocHandle.ts +9 -11
  40. package/src/EphemeralData.ts +2 -2
  41. package/src/Repo.ts +10 -14
  42. package/src/helpers/cbor.ts +4 -4
  43. package/src/helpers/pause.ts +7 -2
  44. package/src/helpers/tests/network-adapter-tests.ts +3 -3
  45. package/src/helpers/withTimeout.ts +2 -2
  46. package/src/index.ts +36 -29
  47. package/src/network/NetworkAdapter.ts +7 -3
  48. package/src/network/NetworkSubsystem.ts +31 -23
  49. package/src/network/messages.ts +88 -151
  50. package/src/storage/StorageSubsystem.ts +8 -8
  51. package/src/synchronizer/CollectionSynchronizer.ts +6 -15
  52. package/src/synchronizer/DocSynchronizer.ts +34 -48
  53. package/src/synchronizer/Synchronizer.ts +2 -2
  54. package/src/types.ts +8 -3
  55. package/test/CollectionSynchronizer.test.ts +58 -53
  56. package/test/DocHandle.test.ts +35 -36
  57. package/test/DocSynchronizer.test.ts +9 -8
  58. package/test/Network.test.ts +1 -0
  59. package/test/Repo.test.ts +177 -97
  60. package/test/StorageSubsystem.test.ts +6 -9
  61. package/test/tsconfig.json +8 -0
  62. package/typedoc.json +3 -3
  63. package/.mocharc.json +0 -5
@@ -1,21 +1,13 @@
1
- export function isValidMessage(message) {
2
- return (typeof message === "object" &&
3
- typeof message.type === "string" &&
4
- typeof message.senderId === "string" &&
5
- (isSyncMessage(message) ||
6
- isEphemeralMessage(message) ||
7
- isRequestMessage(message) ||
8
- isDocumentUnavailableMessage(message)));
9
- }
10
- export function isDocumentUnavailableMessage(message) {
11
- return message.type === "doc-unavailable";
12
- }
13
- export function isRequestMessage(message) {
14
- return message.type === "request";
15
- }
16
- export function isSyncMessage(message) {
17
- return message.type === "sync";
18
- }
19
- export function isEphemeralMessage(message) {
20
- return message.type === "ephemeral";
21
- }
1
+ // TYPE GUARDS
2
+ export const isValidRepoMessage = (message) => typeof message === "object" &&
3
+ typeof message.type === "string" &&
4
+ typeof message.senderId === "string" &&
5
+ (isSyncMessage(message) ||
6
+ isEphemeralMessage(message) ||
7
+ isRequestMessage(message) ||
8
+ isDocumentUnavailableMessage(message));
9
+ // prettier-ignore
10
+ export const isDocumentUnavailableMessage = (msg) => msg.type === "doc-unavailable";
11
+ export const isRequestMessage = (msg) => msg.type === "request";
12
+ export const isSyncMessage = (msg) => msg.type === "sync";
13
+ export const isEphemeralMessage = (msg) => msg.type === "ephemeral";
@@ -10,8 +10,8 @@ function keyHash(binary) {
10
10
  return hashHex;
11
11
  }
12
12
  function headsHash(heads) {
13
- let encoder = new TextEncoder();
14
- let headsbinary = mergeArrays(heads.map((h) => encoder.encode(h)));
13
+ const encoder = new TextEncoder();
14
+ const headsbinary = mergeArrays(heads.map((h) => encoder.encode(h)));
15
15
  return keyHash(headsbinary);
16
16
  }
17
17
  export class StorageSubsystem {
@@ -89,18 +89,18 @@ export class StorageSubsystem {
89
89
  if (!this.#shouldSave(documentId, doc)) {
90
90
  return;
91
91
  }
92
- let sourceChunks = this.#chunkInfos.get(documentId) ?? [];
92
+ const sourceChunks = this.#chunkInfos.get(documentId) ?? [];
93
93
  if (this.#shouldCompact(sourceChunks)) {
94
- this.#saveTotal(documentId, doc, sourceChunks);
94
+ void this.#saveTotal(documentId, doc, sourceChunks);
95
95
  }
96
96
  else {
97
- this.#saveIncremental(documentId, doc);
97
+ void this.#saveIncremental(documentId, doc);
98
98
  }
99
99
  this.#storedHeads.set(documentId, A.getHeads(doc));
100
100
  }
101
101
  async remove(documentId) {
102
- this.#storageAdapter.removeRange([documentId, "snapshot"]);
103
- this.#storageAdapter.removeRange([documentId, "incremental"]);
102
+ void this.#storageAdapter.removeRange([documentId, "snapshot"]);
103
+ void this.#storageAdapter.removeRange([documentId, "incremental"]);
104
104
  }
105
105
  #shouldSave(documentId, doc) {
106
106
  const oldHeads = this.#storedHeads.get(documentId);
@@ -1,7 +1,7 @@
1
1
  import { Repo } from "../Repo.js";
2
- import { PeerId, DocumentId } from "../types.js";
2
+ import { DocumentId, PeerId } from "../types.js";
3
3
  import { Synchronizer } from "./Synchronizer.js";
4
- import { SynchronizerMessage } from "../network/messages.js";
4
+ import { RepoMessage } from "../network/messages.js";
5
5
  /** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
6
6
  export declare class CollectionSynchronizer extends Synchronizer {
7
7
  #private;
@@ -11,7 +11,7 @@ export declare class CollectionSynchronizer extends Synchronizer {
11
11
  * When we receive a sync message for a document we haven't got in memory, we
12
12
  * register it with the repo and start synchronizing
13
13
  */
14
- receiveMessage(message: SynchronizerMessage): Promise<void>;
14
+ receiveMessage(message: RepoMessage): Promise<void>;
15
15
  /**
16
16
  * Starts synchronizing the given document with all peers that we share it generously with.
17
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAOjC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAGL,mBAAmB,EAEpB,MAAM,wBAAwB,CAAA;AAG/B,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAiC9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,mBAAmB;IAyBjD;;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;CAQ1B"}
1
+ {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGpD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAiC9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,WAAW;IAyBzC;;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;CAQ1B"}
@@ -1,4 +1,4 @@
1
- import { stringifyAutomergeUrl, } from "../DocUrl.js";
1
+ import { stringifyAutomergeUrl } from "../DocUrl.js";
2
2
  import { DocSynchronizer } from "./DocSynchronizer.js";
3
3
  import { Synchronizer } from "./Synchronizer.js";
4
4
  import debug from "debug";
@@ -85,7 +85,7 @@ export class CollectionSynchronizer extends Synchronizer {
85
85
  this.#peers.add(peerId);
86
86
  for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
87
87
  const { documentId } = docSynchronizer;
88
- this.repo.sharePolicy(peerId, documentId).then(okToShare => {
88
+ void this.repo.sharePolicy(peerId, documentId).then(okToShare => {
89
89
  if (okToShare)
90
90
  docSynchronizer.beginSync([peerId]);
91
91
  });
@@ -1,7 +1,7 @@
1
1
  import { DocHandle } from "../DocHandle.js";
2
+ import { EphemeralMessage, RepoMessage, RequestMessage, SyncMessage } from "../network/messages.js";
2
3
  import { PeerId } from "../types.js";
3
4
  import { Synchronizer } from "./Synchronizer.js";
4
- import { EphemeralMessage, RequestMessage, SynchronizerMessage, SyncMessage } from "../network/messages.js";
5
5
  type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants";
6
6
  /**
7
7
  * DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
@@ -10,13 +10,13 @@ type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants";
10
10
  export declare class DocSynchronizer extends Synchronizer {
11
11
  #private;
12
12
  private handle;
13
- constructor(handle: DocHandle<any>);
13
+ constructor(handle: DocHandle<unknown>);
14
14
  get peerStates(): Record<PeerId, PeerDocumentStatus>;
15
15
  get documentId(): import("../types.js").DocumentId;
16
16
  hasPeer(peerId: PeerId): boolean;
17
17
  beginSync(peerIds: PeerId[]): void;
18
18
  endSync(peerId: PeerId): void;
19
- receiveMessage(message: SynchronizerMessage): void;
19
+ receiveMessage(message: RepoMessage): void;
20
20
  receiveEphemeralMessage(message: EphemeralMessage): void;
21
21
  receiveSyncMessage(message: SyncMessage | RequestMessage): void;
22
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EACL,gBAAgB,EAIhB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAE/B,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAGrE;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAiBnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC;IAoB1C,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IAiHD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA6B3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,mBAAmB;IAkB3C,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA2EzD"}
1
+ {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAChB,WAAW,EAEX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAErE;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAiBnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC;IAoB9C,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IA8FD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IAmC3B,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;CA4EzD"}
@@ -1,9 +1,9 @@
1
1
  import * as A from "@automerge/automerge/next";
2
- import { READY, REQUESTING, UNAVAILABLE, } from "../DocHandle.js";
3
- import { Synchronizer } from "./Synchronizer.js";
2
+ import { decode } from "cbor-x";
4
3
  import debug from "debug";
4
+ import { READY, REQUESTING, UNAVAILABLE, } from "../DocHandle.js";
5
5
  import { isRequestMessage, } from "../network/messages.js";
6
- import { decode } from "cbor-x";
6
+ import { Synchronizer } from "./Synchronizer.js";
7
7
  /**
8
8
  * DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
9
9
  * to bring it inline with all other peers' versions.
@@ -49,18 +49,19 @@ export class DocSynchronizer extends Synchronizer {
49
49
  return;
50
50
  this.#peers.forEach(peerId => this.#sendSyncMessage(peerId, doc));
51
51
  }
52
- async #broadcastToPeers({ data }) {
52
+ async #broadcastToPeers({ data, }) {
53
53
  this.#log(`broadcastToPeers`, this.#peers);
54
54
  this.#peers.forEach(peerId => this.#sendEphemeralMessage(peerId, data));
55
55
  }
56
56
  #sendEphemeralMessage(peerId, data) {
57
57
  this.#log(`sendEphemeralMessage ->${peerId}`);
58
- this.emit("message", {
58
+ const message = {
59
59
  type: "ephemeral",
60
60
  targetId: peerId,
61
61
  documentId: this.handle.documentId,
62
62
  data,
63
- });
63
+ };
64
+ this.emit("message", message);
64
65
  }
65
66
  #getSyncState(peerId) {
66
67
  if (!this.#peers.includes(peerId)) {
@@ -85,10 +86,9 @@ export class DocSynchronizer extends Synchronizer {
85
86
  const [newSyncState, message] = A.generateSyncMessage(doc, syncState);
86
87
  this.#setSyncState(peerId, newSyncState);
87
88
  if (message) {
88
- this.#logMessage(`sendSyncMessage 🡒 ${peerId}`, message);
89
- const decoded = A.decodeSyncMessage(message);
89
+ const isNew = A.getHeads(doc).length === 0;
90
90
  if (!this.handle.isReady() &&
91
- decoded.heads.length === 0 &&
91
+ isNew &&
92
92
  newSyncState.sharedHeads.length === 0 &&
93
93
  !Object.values(this.#peerDocumentStatuses).includes("has") &&
94
94
  this.#peerDocumentStatuses[peerId] === "unknown") {
@@ -109,30 +109,17 @@ export class DocSynchronizer extends Synchronizer {
109
109
  });
110
110
  }
111
111
  // if we have sent heads, then the peer now has or will have the document
112
- if (decoded.heads.length > 0) {
112
+ if (!isNew) {
113
113
  this.#peerDocumentStatuses[peerId] = "has";
114
114
  }
115
115
  }
116
116
  }
117
- #logMessage = (label, message) => {
118
- // This is real expensive...
119
- return;
120
- const size = message.byteLength;
121
- const logText = `${label} ${size}b`;
122
- const decoded = A.decodeSyncMessage(message);
123
- this.#conciseLog(logText);
124
- this.#log(logText, decoded);
125
- // expanding is expensive, so only do it if we're logging at this level
126
- const expanded = this.#opsLog.enabled
127
- ? decoded.changes.flatMap((change) => A.decodeChange(change).ops.map((op) => JSON.stringify(op)))
128
- : null;
129
- this.#opsLog(logText, expanded);
130
- };
131
117
  /// PUBLIC
132
118
  hasPeer(peerId) {
133
119
  return this.#peers.includes(peerId);
134
120
  }
135
121
  beginSync(peerIds) {
122
+ const newPeers = new Set(peerIds.filter(peerId => !this.#peers.includes(peerId)));
136
123
  this.#log(`beginSync: ${peerIds.join(", ")}`);
137
124
  // HACK: if we have a sync state already, we round-trip it through the encoding system to make
138
125
  // sure state is preserved. This prevents an infinite loop caused by failed attempts to send
@@ -149,10 +136,15 @@ export class DocSynchronizer extends Synchronizer {
149
136
  // we register out peers first, then say that sync has started
150
137
  this.#syncStarted = true;
151
138
  this.#checkDocUnavailable();
152
- if (doc === undefined)
139
+ const wasUnavailable = doc === undefined;
140
+ if (wasUnavailable && newPeers.size == 0) {
153
141
  return;
142
+ }
143
+ // If the doc is unavailable we still need a blank document to generate
144
+ // the sync message from
145
+ const theDoc = doc ?? A.init();
154
146
  peerIds.forEach(peerId => {
155
- this.#sendSyncMessage(peerId, doc);
147
+ this.#sendSyncMessage(peerId, theDoc);
156
148
  });
157
149
  });
158
150
  }
@@ -181,7 +173,7 @@ export class DocSynchronizer extends Synchronizer {
181
173
  if (message.documentId !== this.handle.documentId)
182
174
  throw new Error(`channelId doesn't match documentId`);
183
175
  const { senderId, data } = message;
184
- const contents = decode(data);
176
+ const contents = decode(new Uint8Array(data));
185
177
  this.handle.emit("ephemeral-message", {
186
178
  handle: this.handle,
187
179
  senderId,
@@ -234,11 +226,12 @@ export class DocSynchronizer extends Synchronizer {
234
226
  this.#peers
235
227
  .filter(peerId => this.#peerDocumentStatuses[peerId] === "wants")
236
228
  .forEach(peerId => {
237
- this.emit("message", {
229
+ const message = {
238
230
  type: "doc-unavailable",
239
231
  documentId: this.handle.documentId,
240
232
  targetId: peerId,
241
- });
233
+ };
234
+ this.emit("message", message);
242
235
  });
243
236
  this.handle.unavailable();
244
237
  }
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
- import { Message, MessageContents } from "../network/messages.js";
2
+ import { RepoMessage, MessageContents } from "../network/messages.js";
3
3
  export declare abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
4
- abstract receiveMessage(message: Message): void;
4
+ abstract receiveMessage(message: RepoMessage): void;
5
5
  }
6
6
  export interface SynchronizerEvents {
7
7
  message: (arg: MessageContents) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAEjE,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;CAChD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC"}
1
+ {"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAErE,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC"}
package/dist/types.d.ts CHANGED
@@ -20,6 +20,10 @@ export type BinaryDocumentId = Uint8Array & {
20
20
  };
21
21
  /** A branded type for peer IDs */
22
22
  export type PeerId = string & {
23
- __peerId: false;
23
+ __peerId: true;
24
+ };
25
+ /** A randomly generated string created when the {@link Repo} starts up */
26
+ export type SessionId = string & {
27
+ __SessionId: true;
24
28
  };
25
29
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;GACG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,YAAY,EAAE,IAAI,CAAA;CAAE,CAAA;AACxD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAC3D;GACG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAAE,kBAAkB,EAAE,IAAI,CAAA;CAAE,CAAA;AAExE,kCAAkC;AAClC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;GACG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,YAAY,EAAE,IAAI,CAAA;CAAE,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAE3D;GACG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAAE,kBAAkB,EAAE,IAAI,CAAA;CAAE,CAAA;AAExE,kCAAkC;AAClC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAA;AAEhD,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo",
3
- "version": "1.0.6",
3
+ "version": "1.0.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>",
@@ -11,7 +11,7 @@
11
11
  "build": "tsc",
12
12
  "watch": "npm-watch build",
13
13
  "test:coverage": "c8 --reporter=lcov --reporter=html --reporter=text yarn test",
14
- "test": "mocha --no-warnings --experimental-specifier-resolution=node --exit",
14
+ "test": "vitest",
15
15
  "test:watch": "npm-watch test",
16
16
  "test:log": "cross-env DEBUG='automerge-repo:*' yarn test",
17
17
  "fuzz": "ts-node --esm --experimentalSpecifierResolution=node fuzz/fuzz.ts"
@@ -20,18 +20,10 @@
20
20
  "crypto": false
21
21
  },
22
22
  "devDependencies": {
23
- "@automerge/automerge": "^2.1.0",
24
- "@types/debug": "^4.1.7",
25
- "@types/node": "^20.4.8",
26
- "@types/uuid": "^8.3.4",
27
- "@types/ws": "^8.5.3",
28
- "@typescript-eslint/eslint-plugin": "^5.33.0",
29
- "@typescript-eslint/parser": "^5.33.0",
30
- "http-server": "^14.1.0",
31
- "typescript": "^5.1.6"
23
+ "http-server": "^14.1.0"
32
24
  },
33
25
  "peerDependencies": {
34
- "@automerge/automerge": "^2.1.0"
26
+ "@automerge/automerge": "^2.1.5"
35
27
  },
36
28
  "dependencies": {
37
29
  "bs58check": "^3.0.1",
@@ -65,5 +57,5 @@
65
57
  "publishConfig": {
66
58
  "access": "public"
67
59
  },
68
- "gitHead": "088696831fed48c7a8a88109acd0465222dc8522"
60
+ "gitHead": "71060981f168e511a99ab85b155a54a13fd04bcc"
69
61
  }
package/src/DocHandle.ts CHANGED
@@ -290,14 +290,12 @@ export class DocHandle<T> //
290
290
  async doc(
291
291
  awaitStates: HandleState[] = [READY, UNAVAILABLE]
292
292
  ): Promise<A.Doc<T> | undefined> {
293
- await pause() // yield one tick because reasons
294
293
  try {
295
294
  // wait for the document to enter one of the desired states
296
295
  await this.#statePromise(awaitStates)
297
296
  } catch (error) {
298
- if (error instanceof TimeoutError)
299
- throw new Error(`DocHandle: timed out loading ${this.documentId}`)
300
- else throw error
297
+ // if we timed out (or the load has already failed), return undefined
298
+ return undefined
301
299
  }
302
300
  // Return the document
303
301
  return !this.isUnavailable() ? this.#doc : undefined
@@ -431,7 +429,7 @@ export class DocHandle<T> //
431
429
  * a user could have multiple tabs open and would appear as multiple PeerIds.
432
430
  * every message source must have a unique PeerId.
433
431
  */
434
- broadcast(message: any) {
432
+ broadcast(message: unknown) {
435
433
  this.emit("ephemeral-message-outbound", {
436
434
  handle: this,
437
435
  data: encode(message),
@@ -474,14 +472,14 @@ export interface DocHandleChangePayload<T> {
474
472
  patchInfo: A.PatchInfo<T>
475
473
  }
476
474
 
477
- export interface DocHandleEphemeralMessagePayload {
478
- handle: DocHandle<any>
475
+ export interface DocHandleEphemeralMessagePayload<T> {
476
+ handle: DocHandle<T>
479
477
  senderId: PeerId
480
478
  message: unknown
481
479
  }
482
480
 
483
- export interface DocHandleOutboundEphemeralMessagePayload {
484
- handle: DocHandle<any>
481
+ export interface DocHandleOutboundEphemeralMessagePayload<T> {
482
+ handle: DocHandle<T>
485
483
  data: Uint8Array
486
484
  }
487
485
 
@@ -490,9 +488,9 @@ export interface DocHandleEvents<T> {
490
488
  change: (payload: DocHandleChangePayload<T>) => void
491
489
  delete: (payload: DocHandleDeletePayload<T>) => void
492
490
  unavailable: (payload: DocHandleDeletePayload<T>) => void
493
- "ephemeral-message": (payload: DocHandleEphemeralMessagePayload) => void
491
+ "ephemeral-message": (payload: DocHandleEphemeralMessagePayload<T>) => void
494
492
  "ephemeral-message-outbound": (
495
- payload: DocHandleOutboundEphemeralMessagePayload
493
+ payload: DocHandleOutboundEphemeralMessagePayload<T>
496
494
  ) => void
497
495
  }
498
496
 
@@ -1,5 +1,5 @@
1
1
  import { DocumentId, PeerId } from "./index.js"
2
- import { EphemeralMessageContents } from "./network/messages.js"
2
+ import { EphemeralMessage, MessageContents } from "./network/messages.js"
3
3
 
4
4
  // types
5
5
  /** A randomly generated string created when the {@link Repo} starts up */
@@ -12,6 +12,6 @@ export interface EphemeralDataPayload {
12
12
  }
13
13
 
14
14
  export type EphemeralDataMessageEvents = {
15
- message: (event: EphemeralMessageContents) => void
15
+ message: (event: MessageContents<EphemeralMessage>) => void
16
16
  data: (event: EphemeralDataPayload) => void
17
17
  }
package/src/Repo.ts CHANGED
@@ -5,7 +5,7 @@ import { StorageAdapter } from "./storage/StorageAdapter.js"
5
5
  import { StorageSubsystem } from "./storage/StorageSubsystem.js"
6
6
  import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
7
7
  import { type AutomergeUrl, DocumentId, PeerId } from "./types.js"
8
- import { v4 as uuid } from "uuid"
8
+
9
9
  import {
10
10
  parseAutomergeUrl,
11
11
  generateAutomergeUrl,
@@ -106,9 +106,9 @@ export class Repo extends EventEmitter<RepoEvents> {
106
106
  // The synchronizer uses the network subsystem to keep documents in sync with peers.
107
107
  const synchronizer = new CollectionSynchronizer(this)
108
108
 
109
- // When the synchronizer emits sync messages, send them to peers
109
+ // When the synchronizer emits messages, send them to peers
110
110
  synchronizer.on("message", message => {
111
- this.#log(`sending sync message to ${message.targetId}`)
111
+ this.#log(`sending ${message.type} message to ${message.targetId}`)
112
112
  networkSubsystem.send(message)
113
113
  })
114
114
 
@@ -198,9 +198,9 @@ export class Repo extends EventEmitter<RepoEvents> {
198
198
  * @param clonedHandle - The handle to clone
199
199
  *
200
200
  * @remarks This is a wrapper around the `clone` function in the Automerge library.
201
- * The new `DocHandle` will have a new URL but will share history with the original,
202
- * which means that changes made to the cloned handle can be sensibly merged back
203
- * into the original.
201
+ * The new `DocHandle` will have a new URL but will share history with the original,
202
+ * which means that changes made to the cloned handle can be sensibly merged back
203
+ * into the original.
204
204
  *
205
205
  * Any peers this `Repo` is connected to for whom `sharePolicy` returns `true` will
206
206
  * be notified of the newly created DocHandle.
@@ -223,7 +223,7 @@ export class Repo extends EventEmitter<RepoEvents> {
223
223
 
224
224
  const handle = this.create<T>()
225
225
 
226
- handle.update((doc: Automerge.Doc<T>) => {
226
+ handle.update(() => {
227
227
  // we replace the document with the new cloned one
228
228
  return Automerge.clone(sourceDoc)
229
229
  })
@@ -240,7 +240,7 @@ export class Repo extends EventEmitter<RepoEvents> {
240
240
  automergeUrl: AutomergeUrl
241
241
  ): DocHandle<T> {
242
242
  if (!isValidAutomergeUrl(automergeUrl)) {
243
- let maybeAutomergeUrl = parseLegacyUUID(automergeUrl)
243
+ const maybeAutomergeUrl = parseLegacyUUID(automergeUrl)
244
244
  if (maybeAutomergeUrl) {
245
245
  console.warn(
246
246
  "Legacy UUID document ID detected, converting to AutomergeUrl. This will be removed in a future version."
@@ -274,17 +274,13 @@ export class Repo extends EventEmitter<RepoEvents> {
274
274
  /** The documentId of the handle to delete */
275
275
  id: DocumentId | AutomergeUrl
276
276
  ) {
277
- if (isValidAutomergeUrl(id)) {
278
- ;({ documentId: id } = parseAutomergeUrl(id))
279
- }
277
+ if (isValidAutomergeUrl(id)) id = parseAutomergeUrl(id).documentId
280
278
 
281
279
  const handle = this.#getHandle(id, false)
282
280
  handle.delete()
283
281
 
284
282
  delete this.#handleCache[id]
285
- this.emit("delete-document", {
286
- documentId: id,
287
- })
283
+ this.emit("delete-document", { documentId: id })
288
284
  }
289
285
  }
290
286
 
@@ -1,10 +1,10 @@
1
- import { Encoder, decode as cborXdecode } from "cbor-x";
1
+ import { Encoder, decode as cborXdecode } from "cbor-x"
2
2
 
3
- export function encode(obj: any): Buffer {
4
- let encoder = new Encoder({tagUint8Array: false})
3
+ export function encode(obj: unknown): Buffer {
4
+ const encoder = new Encoder({ tagUint8Array: false })
5
5
  return encoder.encode(obj)
6
6
  }
7
7
 
8
- export function decode(buf: Buffer | Uint8Array): any {
8
+ export function decode<T = unknown>(buf: Buffer | Uint8Array): T {
9
9
  return cborXdecode(buf)
10
10
  }
@@ -1,9 +1,14 @@
1
1
  export const pause = (t = 0) =>
2
2
  new Promise<void>(resolve => setTimeout(() => resolve(), t))
3
3
 
4
- export function rejectOnTimeout<T>(promise: Promise<T>, millis: number): Promise<T> {
4
+ export function rejectOnTimeout<T>(
5
+ promise: Promise<T>,
6
+ millis: number
7
+ ): Promise<T> {
5
8
  return Promise.race([
6
9
  promise,
7
- pause(millis).then(() => { throw new Error("timeout exceeded") }),
10
+ pause(millis).then(() => {
11
+ throw new Error("timeout exceeded")
12
+ }),
8
13
  ])
9
14
  }
@@ -1,7 +1,7 @@
1
- import { PeerId, Repo, type NetworkAdapter, DocumentId } from "../../index.js"
1
+ import assert from "assert"
2
+ import { describe, it } from "vitest"
3
+ import { PeerId, Repo, type NetworkAdapter } from "../../index.js"
2
4
  import { eventPromise, eventPromises } from "../eventPromise.js"
3
- import { assert } from "chai"
4
- import { describe, it } from "mocha"
5
5
  import { pause } from "../pause.js"
6
6
 
7
7
  /**
@@ -6,7 +6,7 @@ export const withTimeout = async <T>(
6
6
  promise: Promise<T>,
7
7
  t: number
8
8
  ): Promise<T> => {
9
- let timeoutId: ReturnType<typeof setTimeout>
9
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
10
10
  const timeoutPromise = new Promise<never>((_, reject) => {
11
11
  timeoutId = setTimeout(
12
12
  () => reject(new TimeoutError(`withTimeout: timed out after ${t}ms`)),
@@ -16,7 +16,7 @@ export const withTimeout = async <T>(
16
16
  try {
17
17
  return await Promise.race([promise, timeoutPromise])
18
18
  } finally {
19
- clearTimeout(timeoutId!)
19
+ clearTimeout(timeoutId)
20
20
  }
21
21
  }
22
22
 
package/src/index.ts CHANGED
@@ -16,9 +16,9 @@
16
16
  *
17
17
  * ```typescript
18
18
  * import { Repo } from "@automerge/automerge-repo";
19
- *
19
+ *
20
20
  * const repo = new Repo({
21
- * storage: <storage adapter>,
21
+ * storage: <storage adapter>,
22
22
  * network: [<network adapter>, <network adapter>]
23
23
  * })
24
24
  *
@@ -26,47 +26,54 @@
26
26
  * ```
27
27
  */
28
28
 
29
- export { DocHandle, type HandleState, type DocHandleOptions, type DocHandleEvents } from "./DocHandle.js"
30
- export type {
29
+ export { Repo } from "./Repo.js"
30
+ export { DocHandle } from "./DocHandle.js"
31
+ export { NetworkAdapter } from "./network/NetworkAdapter.js"
32
+ export { StorageAdapter } from "./storage/StorageAdapter.js"
33
+ export {
34
+ isValidAutomergeUrl,
35
+ parseAutomergeUrl,
36
+ stringifyAutomergeUrl,
37
+ } from "./DocUrl.js"
38
+ export { isValidRepoMessage } from "./network/messages.js"
39
+
40
+ /** @hidden **/
41
+ export * as cbor from "./helpers/cbor.js"
42
+
43
+ // types
44
+
45
+ export type {
31
46
  DocHandleChangePayload,
32
47
  DocHandleDeletePayload,
48
+ DocHandleEncodedChangePayload,
33
49
  DocHandleEphemeralMessagePayload,
50
+ DocHandleEvents,
51
+ DocHandleOptions,
34
52
  DocHandleOutboundEphemeralMessagePayload,
35
- DocHandleEncodedChangePayload,
53
+ HandleState,
36
54
  } from "./DocHandle.js"
37
- export { NetworkAdapter } from "./network/NetworkAdapter.js"
38
55
  export type {
56
+ DeleteDocumentPayload,
57
+ DocumentPayload,
58
+ RepoConfig,
59
+ RepoEvents,
60
+ SharePolicy,
61
+ } from "./Repo.js"
62
+ export type {
63
+ NetworkAdapterEvents,
39
64
  OpenPayload,
40
65
  PeerCandidatePayload,
41
66
  PeerDisconnectedPayload,
42
- NetworkAdapterEvents,
43
67
  } from "./network/NetworkAdapter.js"
44
-
45
- // This is a bit confusing right now, but:
46
- // Message is the type for messages used outside of the network adapters
47
- // there are some extra internal network adapter-only messages on NetworkAdapterMessage
48
- // and Message is (as of this writing) a union type for EphmeralMessage and SyncMessage
49
68
  export type {
50
- Message,
51
69
  ArriveMessage,
52
- WelcomeMessage,
53
- NetworkAdapterMessage,
70
+ DocumentUnavailableMessage,
54
71
  EphemeralMessage,
72
+ Message,
73
+ RepoMessage,
55
74
  RequestMessage,
56
- DocumentUnavailableMessage,
57
75
  SyncMessage,
58
- SessionId,
76
+ WelcomeMessage,
59
77
  } from "./network/messages.js"
60
- export { isValidMessage } from "./network/messages.js"
61
-
62
- export { Repo, type SharePolicy, type RepoConfig, type RepoEvents, type DeleteDocumentPayload, type DocumentPayload } from "./Repo.js"
63
- export { StorageAdapter, type StorageKey } from "./storage/StorageAdapter.js"
64
- export {
65
- parseAutomergeUrl,
66
- isValidAutomergeUrl,
67
- stringifyAutomergeUrl as generateAutomergeUrl,
68
- } from "./DocUrl.js"
78
+ export type { StorageKey } from "./storage/StorageAdapter.js"
69
79
  export * from "./types.js"
70
-
71
- /** @hidden **/
72
- export * as cbor from "./helpers/cbor.js"