@automerge/automerge-repo 1.0.5 → 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 (65) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/DocHandle.d.ts +20 -7
  3. package/dist/DocHandle.d.ts.map +1 -1
  4. package/dist/DocHandle.js +27 -7
  5. package/dist/EphemeralData.d.ts +2 -2
  6. package/dist/EphemeralData.d.ts.map +1 -1
  7. package/dist/Repo.d.ts +16 -0
  8. package/dist/Repo.d.ts.map +1 -1
  9. package/dist/Repo.js +38 -10
  10. package/dist/helpers/cbor.d.ts +2 -2
  11. package/dist/helpers/cbor.d.ts.map +1 -1
  12. package/dist/helpers/cbor.js +1 -1
  13. package/dist/helpers/pause.d.ts.map +1 -1
  14. package/dist/helpers/pause.js +3 -1
  15. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  16. package/dist/helpers/tests/network-adapter-tests.js +2 -2
  17. package/dist/index.d.ts +11 -9
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -4
  20. package/dist/network/NetworkAdapter.d.ts +3 -3
  21. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  22. package/dist/network/NetworkSubsystem.d.ts +2 -2
  23. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  24. package/dist/network/NetworkSubsystem.js +30 -18
  25. package/dist/network/messages.d.ts +38 -68
  26. package/dist/network/messages.d.ts.map +1 -1
  27. package/dist/network/messages.js +13 -21
  28. package/dist/storage/StorageSubsystem.js +7 -7
  29. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -3
  30. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  31. package/dist/synchronizer/CollectionSynchronizer.js +2 -2
  32. package/dist/synchronizer/DocSynchronizer.d.ts +3 -3
  33. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  34. package/dist/synchronizer/DocSynchronizer.js +22 -29
  35. package/dist/synchronizer/Synchronizer.d.ts +2 -2
  36. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  37. package/dist/types.d.ts +5 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +5 -13
  40. package/src/DocHandle.ts +38 -14
  41. package/src/EphemeralData.ts +2 -2
  42. package/src/Repo.ts +46 -12
  43. package/src/helpers/cbor.ts +4 -4
  44. package/src/helpers/pause.ts +7 -2
  45. package/src/helpers/tests/network-adapter-tests.ts +3 -3
  46. package/src/helpers/withTimeout.ts +2 -2
  47. package/src/index.ts +36 -29
  48. package/src/network/NetworkAdapter.ts +7 -3
  49. package/src/network/NetworkSubsystem.ts +31 -23
  50. package/src/network/messages.ts +88 -151
  51. package/src/storage/StorageSubsystem.ts +8 -8
  52. package/src/synchronizer/CollectionSynchronizer.ts +6 -15
  53. package/src/synchronizer/DocSynchronizer.ts +34 -48
  54. package/src/synchronizer/Synchronizer.ts +2 -2
  55. package/src/types.ts +8 -3
  56. package/test/CollectionSynchronizer.test.ts +58 -53
  57. package/test/DocHandle.test.ts +35 -36
  58. package/test/DocSynchronizer.test.ts +9 -8
  59. package/test/Network.test.ts +1 -0
  60. package/test/Repo.test.ts +273 -88
  61. package/test/StorageSubsystem.test.ts +6 -9
  62. package/test/tsconfig.json +8 -0
  63. package/test/types.ts +2 -0
  64. package/typedoc.json +3 -3
  65. package/.mocharc.json +0 -5
@@ -1,45 +1,18 @@
1
- import { SessionId } from "../EphemeralData.js";
2
- export { type SessionId } from "../EphemeralData.js";
3
- import { DocumentId, PeerId } from "../types.js";
4
- export declare function isValidMessage(message: NetworkAdapterMessage): message is SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
5
- export declare function isDocumentUnavailableMessage(message: NetworkAdapterMessage): message is DocumentUnavailableMessage;
6
- export declare function isRequestMessage(message: NetworkAdapterMessage): message is RequestMessage;
7
- export declare function isSyncMessage(message: NetworkAdapterMessage): message is SyncMessage;
8
- export declare function isEphemeralMessage(message: NetworkAdapterMessage | MessageContents): message is EphemeralMessage | EphemeralMessageContents;
9
- export interface SyncMessageEnvelope {
10
- senderId: PeerId;
11
- }
12
- export interface SyncMessageContents {
13
- type: "sync";
14
- data: Uint8Array;
15
- targetId: PeerId;
16
- documentId: DocumentId;
17
- }
1
+ import { DocumentId, PeerId, SessionId } from "../types.js";
18
2
  /**
19
3
  * A sync message for a particular document
20
4
  */
21
5
  export type SyncMessage = {
6
+ type: "sync";
22
7
  /** The peer ID of the sender of this message */
23
8
  senderId: PeerId;
24
- type: "sync";
25
- /** The automerge sync message */
26
- data: Uint8Array;
27
9
  /** The peer ID of the recipient of this message */
28
10
  targetId: PeerId;
11
+ /** The automerge sync message */
12
+ data: Uint8Array;
29
13
  /** The document ID of the document this message is for */
30
14
  documentId: DocumentId;
31
15
  };
32
- export interface EphemeralMessageEnvelope {
33
- senderId: PeerId;
34
- count: number;
35
- sessionId: SessionId;
36
- }
37
- export interface EphemeralMessageContents {
38
- type: "ephemeral";
39
- targetId: PeerId;
40
- documentId: DocumentId;
41
- data: Uint8Array;
42
- }
43
16
  /** An ephemeral message
44
17
  *
45
18
  * @remarks
@@ -51,41 +24,30 @@ export interface EphemeralMessageContents {
51
24
  * we have already seen.
52
25
  * */
53
26
  export type EphemeralMessage = {
54
- /** The ID of the peer who sent this message */
27
+ type: "ephemeral";
28
+ /** The peer ID of the sender of this message */
55
29
  senderId: PeerId;
30
+ /** The peer ID of the recipient of this message */
31
+ targetId: PeerId;
56
32
  /** A sequence number which must be incremented for each message sent by this peer */
57
33
  count: number;
58
34
  /** The ID of the session this message is part of. The sequence number for a given session always increases */
59
35
  sessionId: SessionId;
60
- type: "ephemeral";
61
- /** The peer this message is for */
62
- targetId: PeerId;
63
36
  /** The document ID this message pertains to */
64
37
  documentId: DocumentId;
65
38
  /** The actual data of the message */
66
39
  data: Uint8Array;
67
40
  };
68
- export interface DocumentUnavailableMessageContents {
69
- type: "doc-unavailable";
70
- documentId: DocumentId;
71
- targetId: PeerId;
72
- }
73
41
  /** Sent by a {@link Repo} to indicate that it does not have the document and none of it's connected peers do either */
74
42
  export type DocumentUnavailableMessage = {
75
- /** The peer who sent this message */
76
- senderId: PeerId;
77
43
  type: "doc-unavailable";
44
+ /** The peer ID of the sender of this message */
45
+ senderId: PeerId;
46
+ /** The peer ID of the recipient of this message */
47
+ targetId: PeerId;
78
48
  /** The document which the peer claims it doesn't have */
79
49
  documentId: DocumentId;
80
- /** The peer this message is for */
81
- targetId: PeerId;
82
50
  };
83
- export interface RequestMessageContents {
84
- type: "request";
85
- data: Uint8Array;
86
- targetId: PeerId;
87
- documentId: DocumentId;
88
- }
89
51
  /** Sent by a {@link Repo} to request a document from a peer
90
52
  *
91
53
  * @remarks
@@ -93,40 +55,48 @@ export interface RequestMessageContents {
93
55
  * as the initial sync message when asking the other peer if it has the document.
94
56
  * */
95
57
  export type RequestMessage = {
96
- /** The peer who sent this message */
97
- senderId: PeerId;
98
58
  type: "request";
59
+ /** The peer ID of the sender of this message */
60
+ senderId: PeerId;
61
+ /** The peer ID of the recipient of this message */
62
+ targetId: PeerId;
99
63
  /** The initial automerge sync message */
100
64
  data: Uint8Array;
101
- /** The peer this message is for */
102
- targetId: PeerId;
103
65
  /** The document ID this message requests */
104
66
  documentId: DocumentId;
105
67
  };
106
- export type MessageContents = SyncMessageContents | EphemeralMessageContents | RequestMessageContents | DocumentUnavailableMessageContents;
107
- /** The type of messages that {@link Repo} sends and receive to {@link NetworkAdapter}s */
108
- export type Message = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
109
- export type SynchronizerMessage = SyncMessage | RequestMessage | DocumentUnavailableMessage | EphemeralMessage;
110
68
  /** Notify the network that we have arrived so everyone knows our peer ID */
111
69
  export type ArriveMessage = {
112
- /** Our peer ID */
113
- senderId: PeerId;
114
70
  type: "arrive";
71
+ /** The peer ID of the sender of this message */
72
+ senderId: PeerId;
73
+ /** Arrive messages don't have a targetId */
74
+ targetId: never;
115
75
  };
116
76
  /** Respond to an arriving peer with our peer ID */
117
77
  export type WelcomeMessage = {
118
- /** Our peer ID */
78
+ type: "welcome";
79
+ /** The peer ID of the recipient sender this message */
119
80
  senderId: PeerId;
120
- /** The ID of the peer who sent the {@link ArriveMessage} we are responding to */
81
+ /** The peer ID of the recipient of this message */
121
82
  targetId: PeerId;
122
- type: "welcome";
123
83
  };
124
- /** The type of messages that {@link NetworkAdapter}s send and receive to each other
84
+ /** These are message types that a {@link NetworkAdapter} surfaces to a {@link Repo}. */
85
+ export type RepoMessage = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
86
+ /** These are all the message types that a {@link NetworkAdapter} might see.
125
87
  *
126
88
  * @remarks
127
- * It is not _required_ that a {@link NetworkAdapter} use this message type.
128
- * NetworkAdapters are free to use whatever message type makes sense for their
129
- * transport. However, this type is a useful default.
89
+ * It is not _required_ that a {@link NetworkAdapter} use these types: They are free to use
90
+ * whatever message type makes sense for their transport. However, this type is a useful default.
130
91
  * */
131
- export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message;
92
+ export type Message = RepoMessage | ArriveMessage | WelcomeMessage;
93
+ /**
94
+ * The contents of a message, without the sender ID or other properties added by the {@link NetworkSubsystem})
95
+ */
96
+ export type MessageContents<T extends Message = Message> = T extends EphemeralMessage ? Omit<T, "senderId" | "count" | "sessionId"> : Omit<T, "senderId">;
97
+ export declare const isValidRepoMessage: (message: Message) => message is RepoMessage;
98
+ export declare const isDocumentUnavailableMessage: (msg: Message) => msg is DocumentUnavailableMessage;
99
+ export declare const isRequestMessage: (msg: Message) => msg is RequestMessage;
100
+ export declare const isSyncMessage: (msg: Message) => msg is SyncMessage;
101
+ export declare const isEphemeralMessage: (msg: Message) => msg is EphemeralMessage;
132
102
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3D;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IAEZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,iCAAiC;IACjC,IAAI,EAAE,UAAU,CAAA;IAEhB,0DAA0D;IAC1D,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;;;;;KASK;AACL,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,WAAW,CAAA;IAEjB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IAEb,8GAA8G;IAC9G,SAAS,EAAE,SAAS,CAAA;IAEpB,+CAA+C;IAC/C,UAAU,EAAE,UAAU,CAAA;IAEtB,qCAAqC;IACrC,IAAI,EAAE,UAAU,CAAA;CACjB,CAAA;AAED,uHAAuH;AACvH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,iBAAiB,CAAA;IAEvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yDAAyD;IACzD,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;KAKK;AACL,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IAEf,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yCAAyC;IACzC,IAAI,EAAE,UAAU,CAAA;IAEhB,4CAA4C;IAC5C,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,QAAQ,CAAA;IAEd,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,4CAA4C;IAC5C,QAAQ,EAAE,KAAK,CAAA;CAChB,CAAA;AAED,mDAAmD;AACnD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IAEf,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAAA;AAE9B;;;;;KAKK;AACL,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAA;AAElE;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IACrD,CAAC,SAAS,gBAAgB,GACtB,IAAI,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,WAAW,CAAC,GAC3C,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;AAIzB,eAAO,MAAM,kBAAkB,YAAa,OAAO,2BAOT,CAAA;AAG1C,eAAO,MAAM,4BAA4B,QAAS,OAAO,sCACzB,CAAA;AAEhC,eAAO,MAAM,gBAAgB,QAAS,OAAO,0BACrB,CAAA;AAExB,eAAO,MAAM,aAAa,QAAS,OAAO,uBACrB,CAAA;AAErB,eAAO,MAAM,kBAAkB,QAAS,OAAO,4BACrB,CAAA"}
@@ -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.5",
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": "09256d61e0e4e0afd5d8deb1e187b57cb2413e1e"
60
+ "gitHead": "71060981f168e511a99ab85b155a54a13fd04bcc"
69
61
  }
package/src/DocHandle.ts CHANGED
@@ -28,7 +28,7 @@ import { encode } from "./helpers/cbor.js"
28
28
  * A `DocHandle` represents a document which is being managed by a {@link Repo}.
29
29
  * To obtain `DocHandle` use {@link Repo.find} or {@link Repo.create}.
30
30
  *
31
- * To modify the underlying document use either {@link DocHandle.change} or
31
+ * To modify the underlying document use either {@link DocHandle.change} or
32
32
  * {@link DocHandle.changeAt}. These methods will notify the `Repo` that some
33
33
  * change has occured and the `Repo` will save any new changes to the
34
34
  * attached {@link StorageAdapter} and send sync messages to connected peers.
@@ -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
@@ -321,7 +319,7 @@ export class DocHandle<T> //
321
319
  return this.#doc
322
320
  }
323
321
 
324
- /** `update` is called by the repo when we receive changes from the network
322
+ /** `update` is called by the repo when we receive changes from the network
325
323
  * @hidden
326
324
  * */
327
325
  update(callback: (doc: A.Doc<T>) => A.Doc<T>) {
@@ -373,11 +371,37 @@ export class DocHandle<T> //
373
371
  return resultHeads
374
372
  }
375
373
 
374
+ /** Merge another document into this document
375
+ *
376
+ * @param otherHandle - the handle of the document to merge into this one
377
+ *
378
+ * @remarks
379
+ * This is a convenience method for
380
+ * `handle.change(doc => A.merge(doc, otherHandle.docSync()))`. Any peers
381
+ * whom we are sharing changes with will be notified of the changes resulting
382
+ * from the merge.
383
+ *
384
+ * @throws if either document is not ready or if `otherHandle` is unavailable (`otherHandle.docSync() === undefined`)
385
+ */
386
+ merge(otherHandle: DocHandle<T>) {
387
+ if (!this.isReady() || !otherHandle.isReady()) {
388
+ throw new Error("Both handles must be ready to merge")
389
+ }
390
+ const mergingDoc = otherHandle.docSync()
391
+ if (!mergingDoc) {
392
+ throw new Error("The document to be merged in is null, aborting.")
393
+ }
394
+
395
+ this.update(doc => {
396
+ return A.merge(doc, mergingDoc)
397
+ })
398
+ }
399
+
376
400
  unavailable() {
377
401
  this.#machine.send(MARK_UNAVAILABLE)
378
402
  }
379
403
 
380
- /** `request` is called by the repo when the document is not found in storage
404
+ /** `request` is called by the repo when the document is not found in storage
381
405
  * @hidden
382
406
  * */
383
407
  request() {
@@ -405,7 +429,7 @@ export class DocHandle<T> //
405
429
  * a user could have multiple tabs open and would appear as multiple PeerIds.
406
430
  * every message source must have a unique PeerId.
407
431
  */
408
- broadcast(message: any) {
432
+ broadcast(message: unknown) {
409
433
  this.emit("ephemeral-message-outbound", {
410
434
  handle: this,
411
435
  data: encode(message),
@@ -448,14 +472,14 @@ export interface DocHandleChangePayload<T> {
448
472
  patchInfo: A.PatchInfo<T>
449
473
  }
450
474
 
451
- export interface DocHandleEphemeralMessagePayload {
452
- handle: DocHandle<any>
475
+ export interface DocHandleEphemeralMessagePayload<T> {
476
+ handle: DocHandle<T>
453
477
  senderId: PeerId
454
478
  message: unknown
455
479
  }
456
480
 
457
- export interface DocHandleOutboundEphemeralMessagePayload {
458
- handle: DocHandle<any>
481
+ export interface DocHandleOutboundEphemeralMessagePayload<T> {
482
+ handle: DocHandle<T>
459
483
  data: Uint8Array
460
484
  }
461
485
 
@@ -464,9 +488,9 @@ export interface DocHandleEvents<T> {
464
488
  change: (payload: DocHandleChangePayload<T>) => void
465
489
  delete: (payload: DocHandleDeletePayload<T>) => void
466
490
  unavailable: (payload: DocHandleDeletePayload<T>) => void
467
- "ephemeral-message": (payload: DocHandleEphemeralMessagePayload) => void
491
+ "ephemeral-message": (payload: DocHandleEphemeralMessagePayload<T>) => void
468
492
  "ephemeral-message-outbound": (
469
- payload: DocHandleOutboundEphemeralMessagePayload
493
+ payload: DocHandleOutboundEphemeralMessagePayload<T>
470
494
  ) => void
471
495
  }
472
496