@automerge/automerge-repo 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (80) hide show
  1. package/dist/DocCollection.d.ts +4 -2
  2. package/dist/DocCollection.d.ts.map +1 -1
  3. package/dist/DocCollection.js +20 -11
  4. package/dist/DocHandle.d.ts +34 -6
  5. package/dist/DocHandle.d.ts.map +1 -1
  6. package/dist/DocHandle.js +69 -9
  7. package/dist/DocUrl.d.ts +4 -4
  8. package/dist/DocUrl.d.ts.map +1 -1
  9. package/dist/DocUrl.js +9 -9
  10. package/dist/EphemeralData.d.ts +8 -16
  11. package/dist/EphemeralData.d.ts.map +1 -1
  12. package/dist/EphemeralData.js +1 -28
  13. package/dist/Repo.d.ts +0 -2
  14. package/dist/Repo.d.ts.map +1 -1
  15. package/dist/Repo.js +37 -39
  16. package/dist/helpers/cbor.d.ts +4 -0
  17. package/dist/helpers/cbor.d.ts.map +1 -0
  18. package/dist/helpers/cbor.js +8 -0
  19. package/dist/helpers/eventPromise.d.ts +1 -1
  20. package/dist/helpers/eventPromise.d.ts.map +1 -1
  21. package/dist/helpers/headsAreSame.d.ts +0 -1
  22. package/dist/helpers/headsAreSame.d.ts.map +1 -1
  23. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  24. package/dist/helpers/tests/network-adapter-tests.js +15 -13
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/network/NetworkAdapter.d.ts +6 -15
  29. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  30. package/dist/network/NetworkAdapter.js +1 -1
  31. package/dist/network/NetworkSubsystem.d.ts +9 -6
  32. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  33. package/dist/network/NetworkSubsystem.js +69 -32
  34. package/dist/network/messages.d.ts +57 -0
  35. package/dist/network/messages.d.ts.map +1 -0
  36. package/dist/network/messages.js +21 -0
  37. package/dist/storage/StorageSubsystem.d.ts +1 -1
  38. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  39. package/dist/storage/StorageSubsystem.js +2 -2
  40. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -2
  41. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  42. package/dist/synchronizer/CollectionSynchronizer.js +19 -13
  43. package/dist/synchronizer/DocSynchronizer.d.ts +9 -3
  44. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  45. package/dist/synchronizer/DocSynchronizer.js +149 -34
  46. package/dist/synchronizer/Synchronizer.d.ts +4 -5
  47. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  48. package/dist/synchronizer/Synchronizer.js +1 -1
  49. package/dist/types.d.ts +1 -3
  50. package/dist/types.d.ts.map +1 -1
  51. package/fuzz/fuzz.ts +5 -5
  52. package/package.json +3 -3
  53. package/src/DocCollection.ts +23 -12
  54. package/src/DocHandle.ts +120 -13
  55. package/src/DocUrl.ts +10 -10
  56. package/src/EphemeralData.ts +6 -36
  57. package/src/Repo.ts +37 -55
  58. package/src/helpers/cbor.ts +10 -0
  59. package/src/helpers/eventPromise.ts +1 -1
  60. package/src/helpers/headsAreSame.ts +1 -1
  61. package/src/helpers/tests/network-adapter-tests.ts +18 -14
  62. package/src/index.ts +14 -2
  63. package/src/network/NetworkAdapter.ts +6 -22
  64. package/src/network/NetworkSubsystem.ts +94 -44
  65. package/src/network/messages.ts +123 -0
  66. package/src/storage/StorageSubsystem.ts +2 -2
  67. package/src/synchronizer/CollectionSynchronizer.ts +38 -19
  68. package/src/synchronizer/DocSynchronizer.ts +201 -43
  69. package/src/synchronizer/Synchronizer.ts +4 -9
  70. package/src/types.ts +4 -1
  71. package/test/CollectionSynchronizer.test.ts +6 -7
  72. package/test/DocCollection.test.ts +2 -2
  73. package/test/DocHandle.test.ts +32 -17
  74. package/test/DocSynchronizer.test.ts +85 -9
  75. package/test/Repo.test.ts +267 -63
  76. package/test/StorageSubsystem.test.ts +4 -5
  77. package/test/helpers/DummyNetworkAdapter.ts +12 -3
  78. package/test/helpers/DummyStorageAdapter.ts +1 -1
  79. package/tsconfig.json +4 -3
  80. package/test/EphemeralData.test.ts +0 -44
package/dist/Repo.js CHANGED
@@ -1,15 +1,13 @@
1
+ import debug from "debug";
1
2
  import { DocCollection } from "./DocCollection.js";
2
- import { EphemeralData } from "./EphemeralData.js";
3
3
  import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
4
4
  import { StorageSubsystem } from "./storage/StorageSubsystem.js";
5
5
  import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
6
- import debug from "debug";
7
6
  /** A Repo is a DocCollection with networking, syncing, and storage capabilities. */
8
7
  export class Repo extends DocCollection {
9
8
  #log;
10
9
  networkSubsystem;
11
10
  storageSubsystem;
12
- ephemeralData;
13
11
  constructor({ storage, network, peerId, sharePolicy }) {
14
12
  super();
15
13
  this.#log = debug(`automerge-repo:repo`);
@@ -17,36 +15,60 @@ export class Repo extends DocCollection {
17
15
  // DOC COLLECTION
18
16
  // The `document` event is fired by the DocCollection any time we create a new document or look
19
17
  // up a document by ID. We listen for it in order to wire up storage and network synchronization.
20
- this.on("document", async ({ handle }) => {
18
+ this.on("document", async ({ handle, isNew }) => {
21
19
  if (storageSubsystem) {
22
20
  // Save when the document changes
23
21
  handle.on("heads-changed", async ({ handle, doc }) => {
24
22
  await storageSubsystem.saveDoc(handle.documentId, doc);
25
23
  });
26
- // Try to load from disk
27
- const loadedDoc = await storageSubsystem.loadDoc(handle.documentId);
28
- if (loadedDoc) {
29
- handle.update(() => loadedDoc);
24
+ if (isNew) {
25
+ // this is a new document, immediately save it
26
+ await storageSubsystem.saveDoc(handle.documentId, handle.docSync());
27
+ }
28
+ else {
29
+ // Try to load from disk
30
+ const loadedDoc = await storageSubsystem.loadDoc(handle.documentId);
31
+ if (loadedDoc) {
32
+ handle.update(() => loadedDoc);
33
+ }
30
34
  }
31
35
  }
32
- handle.request();
36
+ handle.on("unavailable", () => {
37
+ this.#log("document unavailable", { documentId: handle.documentId });
38
+ this.emit("unavailable-document", {
39
+ documentId: handle.documentId,
40
+ });
41
+ });
42
+ if (this.networkSubsystem.isReady()) {
43
+ handle.request();
44
+ }
45
+ else {
46
+ handle.awaitNetwork();
47
+ this.networkSubsystem.whenReady().then(() => {
48
+ handle.networkReady();
49
+ }).catch(err => {
50
+ this.#log("error waiting for network", { err });
51
+ });
52
+ }
33
53
  // Register the document with the synchronizer. This advertises our interest in the document.
34
54
  synchronizer.addDocument(handle.documentId);
35
55
  });
36
- this.on("delete-document", ({ encodedDocumentId }) => {
56
+ this.on("delete-document", ({ documentId }) => {
37
57
  // TODO Pass the delete on to the network
38
58
  // synchronizer.removeDocument(documentId)
39
59
  if (storageSubsystem) {
40
- storageSubsystem.remove(encodedDocumentId);
60
+ storageSubsystem.remove(documentId).catch(err => {
61
+ this.#log("error deleting document", { documentId, err });
62
+ });
41
63
  }
42
64
  });
43
65
  // SYNCHRONIZER
44
66
  // The synchronizer uses the network subsystem to keep documents in sync with peers.
45
67
  const synchronizer = new CollectionSynchronizer(this);
46
68
  // When the synchronizer emits sync messages, send them to peers
47
- synchronizer.on("message", ({ targetId, channelId, message, broadcast }) => {
48
- this.#log(`sending sync message to ${targetId}`);
49
- networkSubsystem.sendMessage(targetId, channelId, message, broadcast);
69
+ synchronizer.on("message", message => {
70
+ this.#log(`sending sync message to ${message.targetId}`);
71
+ networkSubsystem.send(message);
50
72
  });
51
73
  // STORAGE
52
74
  // The storage subsystem has access to some form of persistence, and deals with save and loading documents.
@@ -67,31 +89,7 @@ export class Repo extends DocCollection {
67
89
  });
68
90
  // Handle incoming messages
69
91
  networkSubsystem.on("message", async (msg) => {
70
- const { senderId, channelId, message } = msg;
71
- // TODO: this demands a more principled way of associating channels with recipients
72
- // Ephemeral channel ids start with "m/"
73
- if (channelId.startsWith("m/")) {
74
- // Ephemeral message
75
- this.#log(`receiving ephemeral message from ${senderId}`);
76
- ephemeralData.receive(senderId, channelId, message);
77
- }
78
- else {
79
- // Sync message
80
- this.#log(`receiving sync message from ${senderId}`);
81
- await synchronizer.receiveSyncMessage(senderId, channelId, message);
82
- }
83
- });
84
- // We establish a special channel for sync messages
85
- networkSubsystem.join();
86
- // EPHEMERAL DATA
87
- // The ephemeral data subsystem uses the network to send and receive messages that are not
88
- // persisted to storage, e.g. cursor position, presence, etc.
89
- const ephemeralData = new EphemeralData();
90
- this.ephemeralData = ephemeralData;
91
- // Send ephemeral messages to peers
92
- ephemeralData.on("message", ({ targetId, channelId, message, broadcast }) => {
93
- this.#log(`sending ephemeral message to ${targetId}`);
94
- networkSubsystem.sendMessage(targetId, channelId, message, broadcast);
92
+ await synchronizer.receiveMessage(msg);
95
93
  });
96
94
  }
97
95
  }
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ export declare function encode(obj: any): Buffer;
3
+ export declare function decode(buf: Buffer | Uint8Array): any;
4
+ //# sourceMappingURL=cbor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cbor.d.ts","sourceRoot":"","sources":["../../src/helpers/cbor.ts"],"names":[],"mappings":";AAEA,wBAAgB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAGvC;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,GAAG,CAEpD"}
@@ -0,0 +1,8 @@
1
+ import { Encoder, decode as cborXdecode } from "cbor-x";
2
+ export function encode(obj) {
3
+ let encoder = new Encoder({ tagUint8Array: false });
4
+ return encoder.encode(obj);
5
+ }
6
+ export function decode(buf) {
7
+ return cborXdecode(buf);
8
+ }
@@ -1,4 +1,4 @@
1
- import EventEmitter from "eventemitter3";
1
+ import { EventEmitter } from "eventemitter3";
2
2
  /** Returns a promise that resolves when the given event is emitted on the given emitter. */
3
3
  export declare const eventPromise: (emitter: EventEmitter, event: string) => Promise<any>;
4
4
  export declare const eventPromises: (emitters: EventEmitter[], event: string) => Promise<any[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"eventPromise.d.ts","sourceRoot":"","sources":["../../src/helpers/eventPromise.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AAExC,4FAA4F;AAC5F,eAAO,MAAM,YAAY,YAAa,YAAY,SAAS,MAAM,iBACE,CAAA;AAEnE,eAAO,MAAM,aAAa,aAAc,YAAY,EAAE,SAAS,MAAM,mBAGpE,CAAA"}
1
+ {"version":3,"file":"eventPromise.d.ts","sourceRoot":"","sources":["../../src/helpers/eventPromise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,YAAY,YAAa,YAAY,SAAS,MAAM,iBACE,CAAA;AAEnE,eAAO,MAAM,aAAa,aAAc,YAAY,EAAE,SAAS,MAAM,mBAGpE,CAAA"}
@@ -1,3 +1,2 @@
1
- import { Heads } from "@automerge/automerge";
2
1
  export declare const headsAreSame: (a: Heads, b: Heads) => boolean;
3
2
  //# sourceMappingURL=headsAreSame.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headsAreSame.d.ts","sourceRoot":"","sources":["../../src/helpers/headsAreSame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,sBAAsB,CAAA;AAG1C,eAAO,MAAM,YAAY,iCAExB,CAAA"}
1
+ {"version":3,"file":"headsAreSame.d.ts","sourceRoot":"","sources":["../../src/helpers/headsAreSame.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,iCAExB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAa,MAAM,gBAAgB,CAAA;AAK7E;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA2HrE;AAID,KAAK,OAAO,GAAG,cAAc,GAAG,cAAc,EAAE,CAAA;AAEhD,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
1
+ {"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAc,MAAM,gBAAgB,CAAA;AAM9E;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA8HrE;AAID,KAAK,OAAO,GAAG,cAAc,GAAG,cAAc,EAAE,CAAA;AAEhD,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
@@ -2,6 +2,7 @@ import { Repo } from "../../index.js";
2
2
  import { eventPromise, eventPromises } from "../eventPromise.js";
3
3
  import { assert } from "chai";
4
4
  import { describe, it } from "mocha";
5
+ import { pause } from "../pause.js";
5
6
  /**
6
7
  * Runs a series of tests against a set of three peers, each represented by one or more instantiated
7
8
  * network adapters.
@@ -38,14 +39,14 @@ export function runAdapterTests(_setup, title) {
38
39
  });
39
40
  // Bob receives the change
40
41
  await eventPromise(bobHandle, "change");
41
- assert.equal((await bobHandle.doc()).foo, "bar");
42
+ assert.equal((await bobHandle.doc())?.foo, "bar");
42
43
  // Bob changes the document
43
44
  bobHandle.change(d => {
44
45
  d.foo = "baz";
45
46
  });
46
47
  // Alice receives the change
47
48
  await eventPromise(aliceHandle, "change");
48
- assert.equal((await aliceHandle.doc()).foo, "baz");
49
+ assert.equal((await aliceHandle.doc())?.foo, "baz");
49
50
  };
50
51
  // Run the test in both directions, in case they're different types of adapters
51
52
  {
@@ -80,32 +81,33 @@ export function runAdapterTests(_setup, title) {
80
81
  });
81
82
  // Bob and Charlie receive the change
82
83
  await eventPromises([bobHandle, charlieHandle], "change");
83
- assert.equal((await bobHandle.doc()).foo, "bar");
84
- assert.equal((await charlieHandle.doc()).foo, "bar");
84
+ assert.equal((await bobHandle.doc())?.foo, "bar");
85
+ assert.equal((await charlieHandle.doc())?.foo, "bar");
85
86
  // Charlie changes the document
86
87
  charlieHandle.change(d => {
87
88
  d.foo = "baz";
88
89
  });
89
90
  // Alice and Bob receive the change
90
91
  await eventPromises([aliceHandle, bobHandle], "change");
91
- assert.equal((await bobHandle.doc()).foo, "baz");
92
- assert.equal((await charlieHandle.doc()).foo, "baz");
92
+ assert.equal((await bobHandle.doc())?.foo, "baz");
93
+ assert.equal((await charlieHandle.doc())?.foo, "baz");
93
94
  teardown();
94
95
  });
95
- // TODO: with BroadcastChannel, this test never ends, because it goes into an infinite loop,
96
- // because the network has cycles (see #92)
97
- it.skip("can broadcast a message", async () => {
96
+ it("can broadcast a message", async () => {
98
97
  const { adapters, teardown } = await setup();
99
98
  const [a, b, c] = adapters;
100
99
  const aliceRepo = new Repo({ network: a, peerId: alice });
101
100
  const bobRepo = new Repo({ network: b, peerId: bob });
102
101
  const charlieRepo = new Repo({ network: c, peerId: charlie });
103
102
  await eventPromises([aliceRepo, bobRepo, charlieRepo].map(r => r.networkSubsystem), "peer");
104
- const channelId = "broadcast";
103
+ const aliceHandle = aliceRepo.create();
104
+ const charlieHandle = charlieRepo.find(aliceHandle.url);
105
+ // pause to give charlie a chance to let alice know it wants the doc
106
+ await pause(100);
105
107
  const alicePresenceData = { presence: "alice" };
106
- aliceRepo.ephemeralData.broadcast(channelId, alicePresenceData);
107
- const { data } = await eventPromise(charlieRepo.ephemeralData, "data");
108
- assert.deepStrictEqual(data, alicePresenceData);
108
+ aliceHandle.broadcast(alicePresenceData);
109
+ const { message } = await eventPromise(charlieHandle, "ephemeral-message");
110
+ assert.deepStrictEqual(message, alicePresenceData);
109
111
  teardown();
110
112
  });
111
113
  });
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ export { DocCollection } from "./DocCollection.js";
2
2
  export { DocHandle, HandleState } from "./DocHandle.js";
3
3
  export type { DocHandleChangePayload } from "./DocHandle.js";
4
4
  export { NetworkAdapter } from "./network/NetworkAdapter.js";
5
- export type { InboundMessagePayload, MessagePayload, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, } from "./network/NetworkAdapter.js";
5
+ export type { OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, } from "./network/NetworkAdapter.js";
6
+ export type { Message, NetworkAdapterMessage, EphemeralMessage, SyncMessage, } from "./network/messages.js";
6
7
  export { NetworkSubsystem } from "./network/NetworkSubsystem.js";
7
8
  export { Repo, type SharePolicy } from "./Repo.js";
8
9
  export { StorageAdapter, type StorageKey } from "./storage/StorageAdapter.js";
@@ -10,4 +11,5 @@ export { StorageSubsystem } from "./storage/StorageSubsystem.js";
10
11
  export { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
11
12
  export { parseAutomergeUrl, isValidAutomergeUrl, stringifyAutomergeUrl as generateAutomergeUrl, } from "./DocUrl.js";
12
13
  export * from "./types.js";
14
+ export * as cbor from "./helpers/cbor.js";
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,IAAI,oBAAoB,GAC9C,MAAM,aAAa,CAAA;AACpB,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,6BAA6B,CAAA;AAMpC,YAAY,EACV,OAAO,EACP,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,IAAI,oBAAoB,GAC9C,MAAM,aAAa,CAAA;AACpB,cAAc,YAAY,CAAA;AAE1B,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -8,3 +8,4 @@ export { StorageSubsystem } from "./storage/StorageSubsystem.js";
8
8
  export { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
9
9
  export { parseAutomergeUrl, isValidAutomergeUrl, stringifyAutomergeUrl as generateAutomergeUrl, } from "./DocUrl.js";
10
10
  export * from "./types.js";
11
+ export * as cbor from "./helpers/cbor.js";
@@ -1,18 +1,19 @@
1
- import EventEmitter from "eventemitter3";
2
- import { PeerId, ChannelId } from "../types.js";
1
+ import { EventEmitter } from "eventemitter3";
2
+ import { PeerId } from "../types.js";
3
+ import { Message } from "./messages.js";
3
4
  export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
4
5
  peerId?: PeerId;
5
6
  abstract connect(url?: string): void;
6
- abstract sendMessage(peerId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
7
+ abstract send(message: Message): void;
7
8
  abstract join(): void;
8
9
  abstract leave(): void;
9
10
  }
10
11
  export interface NetworkAdapterEvents {
11
- open: (payload: OpenPayload) => void;
12
+ ready: (payload: OpenPayload) => void;
12
13
  close: () => void;
13
14
  "peer-candidate": (payload: PeerCandidatePayload) => void;
14
15
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void;
15
- message: (payload: InboundMessagePayload) => void;
16
+ message: (payload: Message) => void;
16
17
  }
17
18
  export interface OpenPayload {
18
19
  network: NetworkAdapter;
@@ -20,16 +21,6 @@ export interface OpenPayload {
20
21
  export interface PeerCandidatePayload {
21
22
  peerId: PeerId;
22
23
  }
23
- export interface MessagePayload {
24
- targetId: PeerId;
25
- channelId: ChannelId;
26
- message: Uint8Array;
27
- broadcast: boolean;
28
- }
29
- export interface InboundMessagePayload extends MessagePayload {
30
- type?: string;
31
- senderId: PeerId;
32
- }
33
24
  export interface PeerDisconnectedPayload {
34
25
  peerId: PeerId;
35
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE/C,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAEpC,QAAQ,CAAC,WAAW,CAClB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,OAAO,GACjB,IAAI;IAEP,QAAQ,CAAC,IAAI,IAAI,IAAI;IAErB,QAAQ,CAAC,KAAK,IAAI,IAAI;CACvB;AAID,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACzD,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,UAAU,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
1
+ {"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAEpC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,QAAQ,CAAC,IAAI,IAAI,IAAI;IAErB,QAAQ,CAAC,KAAK,IAAI,IAAI;CACvB;AAID,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACrC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACzD,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -1,4 +1,4 @@
1
- import EventEmitter from "eventemitter3";
1
+ import { EventEmitter } from "eventemitter3";
2
2
  export class NetworkAdapter extends EventEmitter {
3
3
  peerId; // hmmm, maybe not
4
4
  }
@@ -1,20 +1,23 @@
1
- import EventEmitter from "eventemitter3";
2
- import { InboundMessagePayload, NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
3
- import { ChannelId, PeerId } from "../types.js";
1
+ import { EventEmitter } from "eventemitter3";
2
+ import { PeerId } from "../types.js";
3
+ import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
4
+ import { Message, MessageContents } from "./messages.js";
4
5
  export declare class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
5
6
  #private;
6
- private adapters;
7
7
  peerId: PeerId;
8
8
  constructor(adapters: NetworkAdapter[], peerId?: PeerId);
9
9
  addNetworkAdapter(networkAdapter: NetworkAdapter): void;
10
- sendMessage(peerId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
10
+ send(message: MessageContents): void;
11
11
  join(): void;
12
12
  leave(): void;
13
+ isReady: () => boolean;
14
+ whenReady: () => Promise<void>;
13
15
  }
14
16
  export interface NetworkSubsystemEvents {
15
17
  peer: (payload: PeerPayload) => void;
16
18
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void;
17
- message: (payload: InboundMessagePayload) => void;
19
+ message: (payload: Message) => void;
20
+ "ready": () => void;
18
21
  }
19
22
  export interface PeerPayload {
20
23
  peerId: PeerId;
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,uBAAuB,EACxB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAI/C,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAKtE,OAAO,CAAC,QAAQ;IACT,MAAM;gBADL,QAAQ,EAAE,cAAc,EAAE,EAC3B,MAAM,SAAiB;IAOhC,iBAAiB,CAAC,cAAc,EAAE,cAAc;IAsDhD,WAAW,CACT,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,OAAO;IAkBpB,IAAI;IAKJ,KAAK;CAIN;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,qBAAqB,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;CACf"}
1
+ {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAE7E,OAAO,EAIL,OAAO,EACP,eAAe,EAChB,MAAM,eAAe,CAAA;AAUtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAY/D,MAAM;gBADb,QAAQ,EAAE,cAAc,EAAE,EACnB,MAAM,SAAiB;IAOhC,iBAAiB,CAAC,cAAc,EAAE,cAAc;IAkEhD,IAAI,CAAC,OAAO,EAAE,eAAe;IA2B7B,IAAI;IAKJ,KAAK;IAKL,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,OAAO,KAAK,IAAI,CAAA;IACnC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -1,19 +1,31 @@
1
- import EventEmitter from "eventemitter3";
1
+ import { EventEmitter } from "eventemitter3";
2
+ import { isEphemeralMessage, isValidMessage, } from "./messages.js";
2
3
  import debug from "debug";
4
+ const getEphemeralMessageSource = (message) => `${message.senderId}:${message.sessionId}`;
3
5
  export class NetworkSubsystem extends EventEmitter {
4
- adapters;
5
6
  peerId;
6
7
  #log;
7
8
  #adaptersByPeer = {};
9
+ #count = 0;
10
+ #sessionId = Math.random().toString(36).slice(2);
11
+ #ephemeralSessionCounts = {};
12
+ #readyAdapterCount = 0;
13
+ #adapters = [];
8
14
  constructor(adapters, peerId = randomPeerId()) {
9
15
  super();
10
- this.adapters = adapters;
11
16
  this.peerId = peerId;
12
17
  this.#log = debug(`automerge-repo:network:${this.peerId}`);
13
- this.adapters.forEach(a => this.addNetworkAdapter(a));
18
+ adapters.forEach(a => this.addNetworkAdapter(a));
14
19
  }
15
20
  addNetworkAdapter(networkAdapter) {
16
- networkAdapter.connect(this.peerId);
21
+ this.#adapters.push(networkAdapter);
22
+ networkAdapter.once("ready", () => {
23
+ this.#readyAdapterCount++;
24
+ this.#log("Adapters ready: ", this.#readyAdapterCount, "/", this.#adapters.length);
25
+ if (this.#readyAdapterCount === this.#adapters.length) {
26
+ this.emit("ready");
27
+ }
28
+ });
17
29
  networkAdapter.on("peer-candidate", ({ peerId }) => {
18
30
  this.#log(`peer candidate: ${peerId} `);
19
31
  // TODO: This is where authentication would happen
@@ -29,18 +41,19 @@ export class NetworkSubsystem extends EventEmitter {
29
41
  this.emit("peer-disconnected", { peerId });
30
42
  });
31
43
  networkAdapter.on("message", msg => {
32
- const { senderId, channelId, broadcast, message } = msg;
33
- this.#log(`message from ${senderId}`);
34
- // If we receive a broadcast message from a network adapter we need to re-broadcast it to all
35
- // our other peers. This is the world's worst gossip protocol.
36
- // TODO: This relies on the network forming a tree! If there are cycles, this approach will
37
- // loop messages around forever.
38
- if (broadcast) {
39
- Object.entries(this.#adaptersByPeer)
40
- .filter(([id]) => id !== senderId)
41
- .forEach(([id, peer]) => {
42
- peer.sendMessage(id, channelId, message, broadcast);
43
- });
44
+ if (!isValidMessage(msg)) {
45
+ this.#log(`invalid message: ${JSON.stringify(msg)}`);
46
+ return;
47
+ }
48
+ this.#log(`message from ${msg.senderId}`);
49
+ if (isEphemeralMessage(msg)) {
50
+ const source = getEphemeralMessageSource(msg);
51
+ if (this.#ephemeralSessionCounts[source] === undefined ||
52
+ msg.count > this.#ephemeralSessionCounts[source]) {
53
+ this.#ephemeralSessionCounts[source] = msg.count;
54
+ this.emit("message", msg);
55
+ }
56
+ return;
44
57
  }
45
58
  this.emit("message", msg);
46
59
  });
@@ -52,33 +65,57 @@ export class NetworkSubsystem extends EventEmitter {
52
65
  }
53
66
  });
54
67
  });
68
+ networkAdapter.connect(this.peerId);
55
69
  networkAdapter.join();
56
70
  }
57
- sendMessage(peerId, channelId, message, broadcast) {
58
- if (broadcast) {
59
- Object.entries(this.#adaptersByPeer).forEach(([id, peer]) => {
60
- this.#log(`sending broadcast to ${id}`);
61
- peer.sendMessage(id, channelId, message, true);
62
- });
71
+ send(message) {
72
+ const peer = this.#adaptersByPeer[message.targetId];
73
+ if (!peer) {
74
+ this.#log(`Tried to send message but peer not found: ${message.targetId}`);
75
+ return;
76
+ }
77
+ this.#log(`Sending message to ${message.targetId}`);
78
+ if (isEphemeralMessage(message)) {
79
+ const outbound = "count" in message
80
+ ? message
81
+ : {
82
+ ...message,
83
+ count: ++this.#count,
84
+ sessionId: this.#sessionId,
85
+ senderId: this.peerId,
86
+ };
87
+ this.#log("Ephemeral message", outbound);
88
+ peer.send(outbound);
63
89
  }
64
90
  else {
65
- const peer = this.#adaptersByPeer[peerId];
66
- if (!peer) {
67
- this.#log(`Tried to send message but peer not found: ${peerId}`);
68
- return;
69
- }
70
- this.#log(`Sending message to ${peerId}`);
71
- peer.sendMessage(peerId, channelId, message, false);
91
+ const outbound = { ...message, senderId: this.peerId };
92
+ this.#log("Sync message", outbound);
93
+ peer.send(outbound);
72
94
  }
73
95
  }
74
96
  join() {
75
97
  this.#log(`Joining network`);
76
- this.adapters.forEach(a => a.join());
98
+ this.#adapters.forEach(a => a.join());
77
99
  }
78
100
  leave() {
79
101
  this.#log(`Leaving network`);
80
- this.adapters.forEach(a => a.leave());
102
+ this.#adapters.forEach(a => a.leave());
81
103
  }
104
+ isReady = () => {
105
+ return this.#readyAdapterCount === this.#adapters.length;
106
+ };
107
+ whenReady = async () => {
108
+ if (this.isReady()) {
109
+ return;
110
+ }
111
+ else {
112
+ return new Promise(resolve => {
113
+ this.once("ready", () => {
114
+ resolve();
115
+ });
116
+ });
117
+ }
118
+ };
82
119
  }
83
120
  function randomPeerId() {
84
121
  return `user-${Math.round(Math.random() * 100000)}`;
@@ -0,0 +1,57 @@
1
+ import { SessionId } from "../EphemeralData.js";
2
+ import { DocumentId, PeerId } from "../types.js";
3
+ export declare function isValidMessage(message: NetworkAdapterMessage): message is SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
4
+ export declare function isDocumentUnavailableMessage(message: NetworkAdapterMessage): message is DocumentUnavailableMessage;
5
+ export declare function isRequestMessage(message: NetworkAdapterMessage): message is RequestMessage;
6
+ export declare function isSyncMessage(message: NetworkAdapterMessage): message is SyncMessage;
7
+ export declare function isEphemeralMessage(message: NetworkAdapterMessage | MessageContents): message is EphemeralMessage | EphemeralMessageContents;
8
+ export interface SyncMessageEnvelope {
9
+ senderId: PeerId;
10
+ }
11
+ export interface SyncMessageContents {
12
+ type: "sync";
13
+ data: Uint8Array;
14
+ targetId: PeerId;
15
+ documentId: DocumentId;
16
+ }
17
+ export type SyncMessage = SyncMessageEnvelope & SyncMessageContents;
18
+ export interface EphemeralMessageEnvelope {
19
+ senderId: PeerId;
20
+ count: number;
21
+ sessionId: SessionId;
22
+ }
23
+ export interface EphemeralMessageContents {
24
+ type: "ephemeral";
25
+ targetId: PeerId;
26
+ documentId: DocumentId;
27
+ data: Uint8Array;
28
+ }
29
+ export type EphemeralMessage = EphemeralMessageEnvelope & EphemeralMessageContents;
30
+ export interface DocumentUnavailableMessageContents {
31
+ type: "doc-unavailable";
32
+ documentId: DocumentId;
33
+ targetId: PeerId;
34
+ }
35
+ export type DocumentUnavailableMessage = SyncMessageEnvelope & DocumentUnavailableMessageContents;
36
+ export interface RequestMessageContents {
37
+ type: "request";
38
+ data: Uint8Array;
39
+ targetId: PeerId;
40
+ documentId: DocumentId;
41
+ }
42
+ export type RequestMessage = SyncMessageEnvelope & RequestMessageContents;
43
+ export type MessageContents = SyncMessageContents | EphemeralMessageContents | RequestMessageContents | DocumentUnavailableMessageContents;
44
+ export type Message = SyncMessage | EphemeralMessage | RequestMessage | DocumentUnavailableMessage;
45
+ export type SynchronizerMessage = SyncMessage | RequestMessage | DocumentUnavailableMessage | EphemeralMessage;
46
+ type ArriveMessage = {
47
+ senderId: PeerId;
48
+ type: "arrive";
49
+ };
50
+ type WelcomeMessage = {
51
+ senderId: PeerId;
52
+ targetId: PeerId;
53
+ type: "welcome";
54
+ };
55
+ export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message;
56
+ export {};
57
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IACN,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAU7B;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,0BAA0B,CAEvC;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,cAAc,CAE3B;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,IAAI,WAAW,CAExB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,qBAAqB,GAAG,eAAe,GAC/C,OAAO,IAAI,gBAAgB,GAAG,wBAAwB,CAExD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,mBAAmB,CAAA;AAEnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,GACrD,wBAAwB,CAAA;AAE1B,MAAM,WAAW,kCAAkC;IACjD,IAAI,EAAE,iBAAiB,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAC1D,kCAAkC,CAAA;AAEpC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,mBAAmB,GAAG,sBAAsB,CAAA;AAEzE,MAAM,MAAM,eAAe,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,sBAAsB,GACtB,kCAAkC,CAAA;AAEtC,MAAM,MAAM,OAAO,GACf,WAAW,GACX,gBAAgB,GAChB,cAAc,GACd,0BAA0B,CAAA;AAE9B,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,cAAc,GACd,0BAA0B,GAC1B,gBAAgB,CAAA;AAEpB,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,QAAQ,CAAA;CACf,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAA"}
@@ -0,0 +1,21 @@
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,4 +1,4 @@
1
- import * as A from "@automerge/automerge";
1
+ import * as A from "@automerge/automerge/next";
2
2
  import { StorageAdapter } from "./StorageAdapter.js";
3
3
  import { type DocumentId } from "../types.js";
4
4
  export type ChunkType = "snapshot" | "incremental";
@@ -1 +1 @@
1
- {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAA;AAEhE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAa7C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAelD,qBAAa,gBAAgB;;gBAMf,cAAc,EAAE,cAAc;IAqDpC,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IA0B/D,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAanE,MAAM,CAAC,UAAU,EAAE,UAAU;CAgCpC"}
1
+ {"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAA;AAEhE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAa7C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAelD,qBAAa,gBAAgB;;gBAMf,cAAc,EAAE,cAAc;IAqDpC,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IA0B/D,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAanE,MAAM,CAAC,UAAU,EAAE,UAAU;CAgCpC"}
@@ -1,4 +1,4 @@
1
- import * as A from "@automerge/automerge";
1
+ import * as A from "@automerge/automerge/next";
2
2
  import * as sha256 from "fast-sha256";
3
3
  import { mergeArrays } from "../helpers/mergeArrays.js";
4
4
  import debug from "debug";
@@ -11,7 +11,7 @@ function keyHash(binary) {
11
11
  }
12
12
  function headsHash(heads) {
13
13
  let encoder = new TextEncoder();
14
- let headsbinary = mergeArrays(heads.map(h => encoder.encode(h)));
14
+ let headsbinary = mergeArrays(heads.map((h) => encoder.encode(h)));
15
15
  return keyHash(headsbinary);
16
16
  }
17
17
  export class StorageSubsystem {