@automerge/automerge-repo 1.0.0-alpha.0 → 1.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/DocCollection.d.ts +2 -1
  2. package/dist/DocCollection.d.ts.map +1 -1
  3. package/dist/DocCollection.js +17 -8
  4. package/dist/DocHandle.d.ts +27 -7
  5. package/dist/DocHandle.d.ts.map +1 -1
  6. package/dist/DocHandle.js +47 -23
  7. package/dist/DocUrl.d.ts +3 -3
  8. package/dist/DocUrl.js +9 -9
  9. package/dist/EphemeralData.d.ts +8 -16
  10. package/dist/EphemeralData.d.ts.map +1 -1
  11. package/dist/EphemeralData.js +1 -28
  12. package/dist/Repo.d.ts +0 -2
  13. package/dist/Repo.d.ts.map +1 -1
  14. package/dist/Repo.js +18 -36
  15. package/dist/helpers/headsAreSame.d.ts +2 -2
  16. package/dist/helpers/headsAreSame.d.ts.map +1 -1
  17. package/dist/helpers/headsAreSame.js +1 -4
  18. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  19. package/dist/helpers/tests/network-adapter-tests.js +15 -13
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/network/NetworkAdapter.d.ts +4 -13
  23. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  24. package/dist/network/NetworkSubsystem.d.ts +5 -4
  25. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  26. package/dist/network/NetworkSubsystem.js +39 -25
  27. package/dist/network/messages.d.ts +57 -0
  28. package/dist/network/messages.d.ts.map +1 -0
  29. package/dist/network/messages.js +21 -0
  30. package/dist/storage/StorageSubsystem.d.ts +2 -2
  31. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  32. package/dist/storage/StorageSubsystem.js +36 -6
  33. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -2
  34. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  35. package/dist/synchronizer/CollectionSynchronizer.js +19 -13
  36. package/dist/synchronizer/DocSynchronizer.d.ts +9 -3
  37. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  38. package/dist/synchronizer/DocSynchronizer.js +145 -29
  39. package/dist/synchronizer/Synchronizer.d.ts +3 -4
  40. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  41. package/dist/types.d.ts +1 -3
  42. package/dist/types.d.ts.map +1 -1
  43. package/fuzz/fuzz.ts +4 -4
  44. package/package.json +3 -3
  45. package/src/DocCollection.ts +19 -9
  46. package/src/DocHandle.ts +82 -37
  47. package/src/DocUrl.ts +9 -9
  48. package/src/EphemeralData.ts +6 -36
  49. package/src/Repo.ts +20 -52
  50. package/src/helpers/headsAreSame.ts +3 -5
  51. package/src/helpers/tests/network-adapter-tests.ts +18 -14
  52. package/src/index.ts +12 -2
  53. package/src/network/NetworkAdapter.ts +4 -20
  54. package/src/network/NetworkSubsystem.ts +61 -38
  55. package/src/network/messages.ts +123 -0
  56. package/src/storage/StorageSubsystem.ts +42 -6
  57. package/src/synchronizer/CollectionSynchronizer.ts +38 -19
  58. package/src/synchronizer/DocSynchronizer.ts +196 -38
  59. package/src/synchronizer/Synchronizer.ts +3 -8
  60. package/src/types.ts +4 -1
  61. package/test/CollectionSynchronizer.test.ts +6 -7
  62. package/test/DocHandle.test.ts +36 -22
  63. package/test/DocSynchronizer.test.ts +85 -9
  64. package/test/Repo.test.ts +279 -59
  65. package/test/StorageSubsystem.test.ts +9 -9
  66. package/test/helpers/DummyNetworkAdapter.ts +1 -1
  67. package/tsconfig.json +2 -1
  68. package/test/EphemeralData.test.ts +0 -44
@@ -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";
@@ -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"}
@@ -1,9 +1,10 @@
1
1
  import EventEmitter from "eventemitter3";
2
- import { PeerId, ChannelId } from "../types.js";
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
  }
@@ -12,7 +13,7 @@ export interface NetworkAdapterEvents {
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,YAAY,MAAM,eAAe,CAAA;AACxC,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,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,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,20 +1,21 @@
1
1
  import EventEmitter from "eventemitter3";
2
- import { InboundMessagePayload, NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
3
- import { ChannelId, PeerId } from "../types.js";
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
7
  private adapters;
7
8
  peerId: PeerId;
8
9
  constructor(adapters: NetworkAdapter[], peerId?: PeerId);
9
10
  addNetworkAdapter(networkAdapter: NetworkAdapter): void;
10
- sendMessage(peerId: PeerId, channelId: ChannelId, message: Uint8Array, broadcast: boolean): void;
11
+ send(message: MessageContents): void;
11
12
  join(): void;
12
13
  leave(): void;
13
14
  }
14
15
  export interface NetworkSubsystemEvents {
15
16
  peer: (payload: PeerPayload) => void;
16
17
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void;
17
- message: (payload: InboundMessagePayload) => void;
18
+ message: (payload: Message) => void;
18
19
  }
19
20
  export interface PeerPayload {
20
21
  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,YAAY,MAAM,eAAe,CAAA;AACxC,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;;IAStE,OAAO,CAAC,QAAQ;IACT,MAAM;gBADL,QAAQ,EAAE,cAAc,EAAE,EAC3B,MAAM,SAAiB;IAOhC,iBAAiB,CAAC,cAAc,EAAE,cAAc;IA0DhD,IAAI,CAAC,OAAO,EAAE,eAAe;IA2B7B,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,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -1,10 +1,15 @@
1
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
6
  adapters;
5
7
  peerId;
6
8
  #log;
7
9
  #adaptersByPeer = {};
10
+ #count = 0;
11
+ #sessionId = Math.random().toString(36).slice(2);
12
+ #ephemeralSessionCounts = {};
8
13
  constructor(adapters, peerId = randomPeerId()) {
9
14
  super();
10
15
  this.adapters = adapters;
@@ -29,18 +34,19 @@ export class NetworkSubsystem extends EventEmitter {
29
34
  this.emit("peer-disconnected", { peerId });
30
35
  });
31
36
  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
- });
37
+ if (!isValidMessage(msg)) {
38
+ this.#log(`invalid message: ${JSON.stringify(msg)}`);
39
+ return;
40
+ }
41
+ this.#log(`message from ${msg.senderId}`);
42
+ if (isEphemeralMessage(msg)) {
43
+ const source = getEphemeralMessageSource(msg);
44
+ if (this.#ephemeralSessionCounts[source] === undefined ||
45
+ msg.count > this.#ephemeralSessionCounts[source]) {
46
+ this.#ephemeralSessionCounts[source] = msg.count;
47
+ this.emit("message", msg);
48
+ }
49
+ return;
44
50
  }
45
51
  this.emit("message", msg);
46
52
  });
@@ -54,21 +60,29 @@ export class NetworkSubsystem extends EventEmitter {
54
60
  });
55
61
  networkAdapter.join();
56
62
  }
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
- });
63
+ send(message) {
64
+ const peer = this.#adaptersByPeer[message.targetId];
65
+ if (!peer) {
66
+ this.#log(`Tried to send message but peer not found: ${message.targetId}`);
67
+ return;
68
+ }
69
+ this.#log(`Sending message to ${message.targetId}`);
70
+ if (isEphemeralMessage(message)) {
71
+ const outbound = "count" in message
72
+ ? message
73
+ : {
74
+ ...message,
75
+ count: ++this.#count,
76
+ sessionId: this.#sessionId,
77
+ senderId: this.peerId,
78
+ };
79
+ this.#log("Ephemeral message", outbound);
80
+ peer.send(outbound);
63
81
  }
64
82
  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);
83
+ const outbound = { ...message, senderId: this.peerId };
84
+ this.#log("Sync message", outbound);
85
+ peer.send(outbound);
72
86
  }
73
87
  }
74
88
  join() {
@@ -0,0 +1,57 @@
1
+ import { SessionId } from "../EphemeralData";
2
+ import { DocumentId, PeerId } from "../types";
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,kBAAkB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAE7C,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
+ }
@@ -5,8 +5,8 @@ export type ChunkType = "snapshot" | "incremental";
5
5
  export declare class StorageSubsystem {
6
6
  #private;
7
7
  constructor(storageAdapter: StorageAdapter);
8
- loadBinary(documentId: DocumentId): Promise<Uint8Array>;
9
- save(documentId: DocumentId, doc: A.Doc<unknown>): Promise<void>;
8
+ loadDoc(documentId: DocumentId): Promise<A.Doc<unknown> | null>;
9
+ saveDoc(documentId: DocumentId, doc: A.Doc<unknown>): Promise<void>;
10
10
  remove(documentId: DocumentId): Promise<void>;
11
11
  }
12
12
  //# sourceMappingURL=StorageSubsystem.d.ts.map
@@ -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;AAW7C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;AAelD,qBAAa,gBAAgB;;gBAIf,cAAc,EAAE,cAAc;IA6CpC,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBvD,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE,MAAM,CAAC,UAAU,EAAE,UAAU;CAkBpC"}
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,6 +1,8 @@
1
1
  import * as A from "@automerge/automerge";
2
2
  import * as sha256 from "fast-sha256";
3
3
  import { mergeArrays } from "../helpers/mergeArrays.js";
4
+ import debug from "debug";
5
+ import { headsAreSame } from "../helpers/headsAreSame.js";
4
6
  function keyHash(binary) {
5
7
  const hash = sha256.hash(binary);
6
8
  const hashArray = Array.from(new Uint8Array(hash)); // convert buffer to byte array
@@ -15,13 +17,16 @@ function headsHash(heads) {
15
17
  export class StorageSubsystem {
16
18
  #storageAdapter;
17
19
  #chunkInfos = new Map();
20
+ #storedHeads = new Map();
21
+ #log = debug(`automerge-repo:storage-subsystem`);
18
22
  constructor(storageAdapter) {
19
23
  this.#storageAdapter = storageAdapter;
20
24
  }
21
25
  async #saveIncremental(documentId, doc) {
22
- const binary = A.saveIncremental(doc);
26
+ const binary = A.saveSince(doc, this.#storedHeads.get(documentId) ?? []);
23
27
  if (binary && binary.length > 0) {
24
28
  const key = [documentId, "incremental", keyHash(binary)];
29
+ this.#log(`Saving incremental ${key} for document ${documentId}`);
25
30
  await this.#storageAdapter.save(key, binary);
26
31
  if (!this.#chunkInfos.has(documentId)) {
27
32
  this.#chunkInfos.set(documentId, []);
@@ -31,6 +36,7 @@ export class StorageSubsystem {
31
36
  type: "incremental",
32
37
  size: binary.length,
33
38
  });
39
+ this.#storedHeads.set(documentId, A.getHeads(doc));
34
40
  }
35
41
  else {
36
42
  return Promise.resolve();
@@ -38,8 +44,11 @@ export class StorageSubsystem {
38
44
  }
39
45
  async #saveTotal(documentId, doc, sourceChunks) {
40
46
  const binary = A.save(doc);
41
- const key = [documentId, "snapshot", headsHash(A.getHeads(doc))];
42
- const oldKeys = new Set(sourceChunks.map(c => c.key));
47
+ const snapshotHash = headsHash(A.getHeads(doc));
48
+ const key = [documentId, "snapshot", snapshotHash];
49
+ const oldKeys = new Set(sourceChunks.map(c => c.key).filter(k => k[2] !== snapshotHash));
50
+ this.#log(`Saving snapshot ${key} for document ${documentId}`);
51
+ this.#log(`deleting old chunks ${Array.from(oldKeys)}`);
43
52
  await this.#storageAdapter.save(key, binary);
44
53
  for (const key of oldKeys) {
45
54
  await this.#storageAdapter.remove(key);
@@ -48,7 +57,7 @@ export class StorageSubsystem {
48
57
  newChunkInfos.push({ key, type: "snapshot", size: binary.length });
49
58
  this.#chunkInfos.set(documentId, newChunkInfos);
50
59
  }
51
- async loadBinary(documentId) {
60
+ async loadDoc(documentId) {
52
61
  const loaded = await this.#storageAdapter.loadRange([documentId]);
53
62
  const binaries = [];
54
63
  const chunkInfos = [];
@@ -65,9 +74,18 @@ export class StorageSubsystem {
65
74
  binaries.push(chunk.data);
66
75
  }
67
76
  this.#chunkInfos.set(documentId, chunkInfos);
68
- return mergeArrays(binaries);
77
+ const binary = mergeArrays(binaries);
78
+ if (binary.length === 0) {
79
+ return null;
80
+ }
81
+ const newDoc = A.loadIncremental(A.init(), binary);
82
+ this.#storedHeads.set(documentId, A.getHeads(newDoc));
83
+ return newDoc;
69
84
  }
70
- async save(documentId, doc) {
85
+ async saveDoc(documentId, doc) {
86
+ if (!this.#shouldSave(documentId, doc)) {
87
+ return;
88
+ }
71
89
  let sourceChunks = this.#chunkInfos.get(documentId) ?? [];
72
90
  if (this.#shouldCompact(sourceChunks)) {
73
91
  this.#saveTotal(documentId, doc, sourceChunks);
@@ -75,11 +93,23 @@ export class StorageSubsystem {
75
93
  else {
76
94
  this.#saveIncremental(documentId, doc);
77
95
  }
96
+ this.#storedHeads.set(documentId, A.getHeads(doc));
78
97
  }
79
98
  async remove(documentId) {
80
99
  this.#storageAdapter.remove([documentId, "snapshot"]);
81
100
  this.#storageAdapter.removeRange([documentId, "incremental"]);
82
101
  }
102
+ #shouldSave(documentId, doc) {
103
+ const oldHeads = this.#storedHeads.get(documentId);
104
+ if (!oldHeads) {
105
+ return true;
106
+ }
107
+ const newHeads = A.getHeads(doc);
108
+ if (headsAreSame(newHeads, oldHeads)) {
109
+ return false;
110
+ }
111
+ return true;
112
+ }
83
113
  #shouldCompact(sourceChunks) {
84
114
  // compact if the incremental size is greater than the snapshot size
85
115
  let snapshotSize = 0;
@@ -1,6 +1,7 @@
1
1
  import { DocCollection } from "../DocCollection.js";
2
- import { ChannelId, PeerId, DocumentId } from "../types.js";
2
+ import { PeerId, DocumentId } from "../types.js";
3
3
  import { Synchronizer } from "./Synchronizer.js";
4
+ import { SynchronizerMessage } from "../network/messages.js";
4
5
  /** A CollectionSynchronizer is responsible for synchronizing a DocCollection with peers. */
5
6
  export declare class CollectionSynchronizer extends Synchronizer {
6
7
  #private;
@@ -10,7 +11,7 @@ export declare class CollectionSynchronizer extends Synchronizer {
10
11
  * When we receive a sync message for a document we haven't got in memory, we
11
12
  * register it with the repo and start synchronizing
12
13
  */
13
- receiveSyncMessage(peerId: PeerId, channelId: ChannelId, message: Uint8Array): Promise<void>;
14
+ receiveMessage(message: SynchronizerMessage): Promise<void>;
14
15
  /**
15
16
  * Starts synchronizing the given document with all peers that we share it generously with.
16
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAOnD,OAAO,EAAE,SAAS,EAAoB,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAO1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAiCvC;;;OAGG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU;IAmBrB;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAUlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAWtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;CAQ1B"}
1
+ {"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAOnD,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,aAAa;IAiCvC;;;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"}
@@ -10,6 +10,8 @@ export class CollectionSynchronizer extends Synchronizer {
10
10
  #peers = new Set();
11
11
  /** A map of documentIds to their synchronizers */
12
12
  #docSynchronizers = {};
13
+ /** Used to determine if the document is know to the Collection and a synchronizer exists or is being set up */
14
+ #docSetUp = {};
13
15
  constructor(repo) {
14
16
  super();
15
17
  this.repo = repo;
@@ -44,29 +46,30 @@ export class CollectionSynchronizer extends Synchronizer {
44
46
  * When we receive a sync message for a document we haven't got in memory, we
45
47
  * register it with the repo and start synchronizing
46
48
  */
47
- async receiveSyncMessage(peerId, channelId, message) {
48
- log(`onSyncMessage: ${peerId}, ${channelId}, ${message.byteLength}bytes`);
49
- const documentId = channelId;
49
+ async receiveMessage(message) {
50
+ log(`onSyncMessage: ${message.senderId}, ${message.documentId}, ${"data" in message ? message.data.byteLength + "bytes" : ""}`);
51
+ const documentId = message.documentId;
50
52
  if (!documentId) {
51
53
  throw new Error("received a message with an invalid documentId");
52
54
  }
53
- const docSynchronizer = await this.#fetchDocSynchronizer(documentId);
54
- await docSynchronizer.receiveSyncMessage(peerId, channelId, message);
55
+ this.#docSetUp[documentId] = true;
56
+ const docSynchronizer = this.#fetchDocSynchronizer(documentId);
57
+ docSynchronizer.receiveMessage(message);
55
58
  // Initiate sync with any new peers
56
59
  const peers = await this.#documentGenerousPeers(documentId);
57
- peers
58
- .filter(peerId => !docSynchronizer.hasPeer(peerId))
59
- .forEach(peerId => docSynchronizer.beginSync(peerId));
60
+ docSynchronizer.beginSync(peers.filter(peerId => !docSynchronizer.hasPeer(peerId)));
60
61
  }
61
62
  /**
62
63
  * Starts synchronizing the given document with all peers that we share it generously with.
63
64
  */
64
65
  addDocument(documentId) {
66
+ // HACK: this is a hack to prevent us from adding the same document twice
67
+ if (this.#docSetUp[documentId]) {
68
+ return;
69
+ }
65
70
  const docSynchronizer = this.#fetchDocSynchronizer(documentId);
66
71
  void this.#documentGenerousPeers(documentId).then(peers => {
67
- peers.forEach(peerId => {
68
- docSynchronizer.beginSync(peerId);
69
- });
72
+ docSynchronizer.beginSync(peers);
70
73
  });
71
74
  }
72
75
  // TODO: implement this
@@ -76,12 +79,15 @@ export class CollectionSynchronizer extends Synchronizer {
76
79
  /** Adds a peer and maybe starts synchronizing with them */
77
80
  addPeer(peerId) {
78
81
  log(`adding ${peerId} & synchronizing with them`);
82
+ if (this.#peers.has(peerId)) {
83
+ return;
84
+ }
79
85
  this.#peers.add(peerId);
80
86
  for (const docSynchronizer of Object.values(this.#docSynchronizers)) {
81
87
  const { documentId } = docSynchronizer;
82
- void this.repo.sharePolicy(peerId, documentId).then(okToShare => {
88
+ this.repo.sharePolicy(peerId, documentId).then(okToShare => {
83
89
  if (okToShare)
84
- docSynchronizer.beginSync(peerId);
90
+ docSynchronizer.beginSync([peerId]);
85
91
  });
86
92
  }
87
93
  }
@@ -1,6 +1,8 @@
1
1
  import { DocHandle } from "../DocHandle.js";
2
- import { ChannelId, PeerId } from "../types.js";
2
+ import { PeerId } from "../types.js";
3
3
  import { Synchronizer } from "./Synchronizer.js";
4
+ import { EphemeralMessage, RequestMessage, SynchronizerMessage, SyncMessage } from "../network/messages.js";
5
+ type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants";
4
6
  /**
5
7
  * DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
6
8
  * to bring it inline with all other peers' versions.
@@ -9,10 +11,14 @@ export declare class DocSynchronizer extends Synchronizer {
9
11
  #private;
10
12
  private handle;
11
13
  constructor(handle: DocHandle<any>);
14
+ get peerStates(): Record<PeerId, PeerDocumentStatus>;
12
15
  get documentId(): import("../types.js").DocumentId;
13
16
  hasPeer(peerId: PeerId): boolean;
14
- beginSync(peerId: PeerId): void;
17
+ beginSync(peerIds: PeerId[]): void;
15
18
  endSync(peerId: PeerId): void;
16
- receiveSyncMessage(peerId: PeerId, channelId: ChannelId, message: Uint8Array): void;
19
+ receiveMessage(message: SynchronizerMessage): void;
20
+ receiveEphemeralMessage(message: EphemeralMessage): void;
21
+ receiveSyncMessage(message: SyncMessage | RequestMessage): void;
17
22
  }
23
+ export {};
18
24
  //# sourceMappingURL=DocSynchronizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAqB,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAanC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC;IAgB1C,IAAI,UAAU,qCAEb;IAwED,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,MAAM,EAAE,MAAM;IAkBxB,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,UAAU;CAsCtB"}
1
+ {"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,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;IA8B3B,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"}