cojson 0.7.35-guest-auth.5 → 0.7.35

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 (85) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +3 -8
  3. package/dist/PeerState.js +58 -0
  4. package/dist/PeerState.js.map +1 -0
  5. package/dist/PriorityBasedMessageQueue.js +51 -0
  6. package/dist/PriorityBasedMessageQueue.js.map +1 -0
  7. package/dist/base64url.js.map +1 -1
  8. package/dist/coValue.js.map +1 -1
  9. package/dist/coValueCore.js +9 -0
  10. package/dist/coValueCore.js.map +1 -1
  11. package/dist/coValues/account.js +3 -2
  12. package/dist/coValues/account.js.map +1 -1
  13. package/dist/coValues/coList.js.map +1 -1
  14. package/dist/coValues/coMap.js.map +1 -1
  15. package/dist/coValues/coStream.js +14 -15
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/coValues/group.js +8 -8
  18. package/dist/coValues/group.js.map +1 -1
  19. package/dist/coreToCoValue.js.map +1 -1
  20. package/dist/crypto/PureJSCrypto.js.map +1 -1
  21. package/dist/crypto/WasmCrypto.js.map +1 -1
  22. package/dist/crypto/crypto.js +0 -3
  23. package/dist/crypto/crypto.js.map +1 -1
  24. package/dist/index.js +4 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/jsonStringify.js.map +1 -1
  27. package/dist/localNode.js +9 -9
  28. package/dist/localNode.js.map +1 -1
  29. package/dist/permissions.js.map +1 -1
  30. package/dist/priority.js +31 -0
  31. package/dist/priority.js.map +1 -0
  32. package/dist/storage/FileSystem.js.map +1 -1
  33. package/dist/storage/chunksAndKnownStates.js +2 -0
  34. package/dist/storage/chunksAndKnownStates.js.map +1 -1
  35. package/dist/storage/index.js.map +1 -1
  36. package/dist/streamUtils.js.map +1 -1
  37. package/dist/sync.js +7 -18
  38. package/dist/sync.js.map +1 -1
  39. package/dist/tests/PeerState.test.js +80 -0
  40. package/dist/tests/PeerState.test.js.map +1 -0
  41. package/dist/tests/PriorityBasedMessageQueue.test.js +97 -0
  42. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -0
  43. package/dist/tests/account.test.js +2 -1
  44. package/dist/tests/account.test.js.map +1 -1
  45. package/dist/tests/coMap.test.js.map +1 -1
  46. package/dist/tests/coStream.test.js +34 -1
  47. package/dist/tests/coStream.test.js.map +1 -1
  48. package/dist/tests/permissions.test.js +42 -41
  49. package/dist/tests/permissions.test.js.map +1 -1
  50. package/dist/tests/priority.test.js +61 -0
  51. package/dist/tests/priority.test.js.map +1 -0
  52. package/dist/tests/sync.test.js +328 -16
  53. package/dist/tests/sync.test.js.map +1 -1
  54. package/dist/tests/testUtils.js +2 -1
  55. package/dist/tests/testUtils.js.map +1 -1
  56. package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  57. package/dist/typeUtils/expectGroup.js.map +1 -1
  58. package/dist/typeUtils/isAccountID.js.map +1 -1
  59. package/package.json +3 -3
  60. package/src/PeerState.ts +74 -0
  61. package/src/PriorityBasedMessageQueue.ts +77 -0
  62. package/src/coValueCore.ts +18 -7
  63. package/src/coValues/account.ts +6 -5
  64. package/src/coValues/coList.ts +4 -4
  65. package/src/coValues/coMap.ts +3 -3
  66. package/src/coValues/coStream.ts +29 -26
  67. package/src/coValues/group.ts +11 -15
  68. package/src/crypto/crypto.ts +0 -5
  69. package/src/ids.ts +2 -2
  70. package/src/index.ts +7 -5
  71. package/src/localNode.ts +18 -18
  72. package/src/permissions.ts +5 -5
  73. package/src/priority.ts +39 -0
  74. package/src/storage/chunksAndKnownStates.ts +2 -0
  75. package/src/sync.ts +19 -34
  76. package/src/tests/PeerState.test.ts +92 -0
  77. package/src/tests/PriorityBasedMessageQueue.test.ts +111 -0
  78. package/src/tests/account.test.ts +2 -1
  79. package/src/tests/coStream.test.ts +58 -1
  80. package/src/tests/permissions.test.ts +42 -41
  81. package/src/tests/priority.test.ts +75 -0
  82. package/src/tests/sync.test.ts +491 -28
  83. package/src/tests/testUtils.ts +2 -1
  84. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +3 -3
  85. package/src/typeUtils/isAccountID.ts +2 -2
@@ -0,0 +1,61 @@
1
+ import { expect, test, describe } from "vitest";
2
+ import { WasmCrypto } from "../index.js";
3
+ import { LocalNode } from "../localNode.js";
4
+ import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
5
+ import { getPriorityFromHeader, CO_VALUE_PRIORITY } from "../priority.js";
6
+ const Crypto = await WasmCrypto.create();
7
+ describe("getPriorityFromHeader", () => {
8
+ test("returns MEDIUM priority for boolean or undefined headers", () => {
9
+ expect(getPriorityFromHeader(true)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
10
+ expect(getPriorityFromHeader(false)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
11
+ expect(getPriorityFromHeader(undefined)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
12
+ });
13
+ test("returns MEDIUM priority for costream type", () => {
14
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
15
+ const costream = node.createCoValue({
16
+ type: "costream",
17
+ ruleset: { type: "unsafeAllowAll" },
18
+ meta: null,
19
+ ...Crypto.createdNowUnique(),
20
+ });
21
+ expect(getPriorityFromHeader(costream.header)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
22
+ });
23
+ test("returns LOW priority for binary costream type", () => {
24
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
25
+ const costream = node.createCoValue({
26
+ type: "costream",
27
+ ruleset: { type: "unsafeAllowAll" },
28
+ meta: { type: "binary" },
29
+ ...Crypto.createdNowUnique(),
30
+ });
31
+ expect(getPriorityFromHeader(costream.header)).toEqual(CO_VALUE_PRIORITY.LOW);
32
+ });
33
+ test("returns HIGH priority for account type", async () => {
34
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
35
+ const account = node.createAccount(node.crypto.newRandomAgentSecret());
36
+ expect(getPriorityFromHeader(account.core.header)).toEqual(CO_VALUE_PRIORITY.HIGH);
37
+ });
38
+ test("returns HIGH priority for group type", () => {
39
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
40
+ const group = node.createGroup();
41
+ expect(getPriorityFromHeader(group.core.header)).toEqual(CO_VALUE_PRIORITY.HIGH);
42
+ });
43
+ test("returns MEDIUM priority for other types", () => {
44
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
45
+ const comap = node.createCoValue({
46
+ type: "comap",
47
+ ruleset: { type: "unsafeAllowAll" },
48
+ meta: null,
49
+ ...Crypto.createdNowUnique(),
50
+ });
51
+ const colist = node.createCoValue({
52
+ type: "colist",
53
+ ruleset: { type: "unsafeAllowAll" },
54
+ meta: null,
55
+ ...Crypto.createdNowUnique(),
56
+ });
57
+ expect(getPriorityFromHeader(comap.header)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
58
+ expect(getPriorityFromHeader(colist.header)).toEqual(CO_VALUE_PRIORITY.MEDIUM);
59
+ });
60
+ });
61
+ //# sourceMappingURL=priority.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priority.test.js","sourceRoot":"","sources":["../../src/tests/priority.test.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,13 @@
1
- import { expect, test } from "vitest";
1
+ import { expect, test, describe } from "vitest";
2
2
  import { LocalNode } from "../localNode.js";
3
+ import { RawCoMap } from "../coValues/coMap.js";
3
4
  import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
4
5
  import { connectedPeers, newQueuePair } from "../streamUtils.js";
5
6
  import { stableStringify } from "../jsonStringify.js";
6
7
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
7
8
  import { expectMap } from "../coValue.js";
9
+ import { newRandomSessionID } from "../coValueCore.js";
10
+ import { getPriorityFromHeader } from "../priority.js";
8
11
  const Crypto = await WasmCrypto.create();
9
12
  test("Node replies with initial tx and header to empty subscribe", async () => {
10
13
  const [admin, session] = randomAnonymousAccountAndSessionID();
@@ -38,16 +41,17 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
38
41
  // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
39
42
  expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
40
43
  const newContentMsg = (await outRxQ.next()).value;
44
+ const expectedHeader = {
45
+ type: "comap",
46
+ ruleset: { type: "ownedByGroup", group: group.id },
47
+ meta: null,
48
+ createdAt: map.core.header.createdAt,
49
+ uniqueness: map.core.header.uniqueness,
50
+ };
41
51
  expect(newContentMsg).toEqual({
42
52
  action: "content",
43
53
  id: map.core.id,
44
- header: {
45
- type: "comap",
46
- ruleset: { type: "ownedByGroup", group: group.id },
47
- meta: null,
48
- createdAt: map.core.header.createdAt,
49
- uniqueness: map.core.header.uniqueness,
50
- },
54
+ header: expectedHeader,
51
55
  new: {
52
56
  [node.currentSessionID]: {
53
57
  after: 0,
@@ -69,6 +73,7 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
69
73
  .lastSignature,
70
74
  },
71
75
  },
76
+ priority: getPriorityFromHeader(map.core.header),
72
77
  });
73
78
  });
74
79
  test("Node replies with only new tx to subscribe with some known state", async () => {
@@ -131,6 +136,7 @@ test("Node replies with only new tx to subscribe with some known state", async (
131
136
  .lastSignature,
132
137
  },
133
138
  },
139
+ priority: getPriorityFromHeader(map.core.header),
134
140
  });
135
141
  });
136
142
  test.todo("TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues");
@@ -172,6 +178,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
172
178
  id: map.core.id,
173
179
  header: map.core.header,
174
180
  new: {},
181
+ priority: getPriorityFromHeader(map.core.header),
175
182
  });
176
183
  map.set("hello", "world", "trusting");
177
184
  const mapEditMsg1 = (await outRxQ.next()).value;
@@ -199,6 +206,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
199
206
  .lastSignature,
200
207
  },
201
208
  },
209
+ priority: getPriorityFromHeader(map.core.header),
202
210
  });
203
211
  map.set("goodbye", "world", "trusting");
204
212
  const mapEditMsg2 = (await outRxQ.next()).value;
@@ -226,6 +234,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
226
234
  .lastSignature,
227
235
  },
228
236
  },
237
+ priority: getPriorityFromHeader(map.core.header),
229
238
  });
230
239
  });
231
240
  test("Client replies with known new content to tellKnownState from server", async () => {
@@ -288,6 +297,7 @@ test("Client replies with known new content to tellKnownState from server", asyn
288
297
  .lastSignature,
289
298
  },
290
299
  },
300
+ priority: getPriorityFromHeader(map.core.header),
291
301
  });
292
302
  });
293
303
  test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
@@ -328,6 +338,7 @@ test("No matter the optimistic known state, node respects invalid known state me
328
338
  id: map.core.id,
329
339
  header: map.core.header,
330
340
  new: {},
341
+ priority: getPriorityFromHeader(map.core.header),
331
342
  });
332
343
  map.set("hello", "world", "trusting");
333
344
  map.set("goodbye", "world", "trusting");
@@ -368,6 +379,7 @@ test("No matter the optimistic known state, node respects invalid known state me
368
379
  .lastSignature,
369
380
  },
370
381
  },
382
+ priority: getPriorityFromHeader(map.core.header),
371
383
  });
372
384
  });
373
385
  test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
@@ -450,6 +462,7 @@ test.todo("If we add a server peer, all updates to all coValues are sent to it,
450
462
  lastSignature: map.core.sessionLogs.get(node.currentSessionID).lastSignature,
451
463
  },
452
464
  },
465
+ priority: getPriorityFromHeader(map.core.header),
453
466
  });
454
467
  });
455
468
  test.skip("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
@@ -488,6 +501,7 @@ test.skip("If we add a server peer, newly created coValues are auto-subscribed t
488
501
  id: map.core.id,
489
502
  header: map.core.header,
490
503
  new: {},
504
+ priority: getPriorityFromHeader(map.core.header),
491
505
  });
492
506
  });
493
507
  test.todo("TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it");
@@ -564,7 +578,7 @@ test.skip("When replaying creation and transactions of a coValue as new content,
564
578
  role: "server",
565
579
  crashOnClose: true,
566
580
  });
567
- const node2 = new LocalNode(admin, Crypto.newRandomSessionID(admin.id), Crypto);
581
+ const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
568
582
  const [inRx2, inTx2] = newQueuePair();
569
583
  const [outRx2, outTx2] = newQueuePair();
570
584
  const outRxQ2 = outRx2[Symbol.asyncIterator]();
@@ -612,6 +626,7 @@ test.skip("When replaying creation and transactions of a coValue as new content,
612
626
  id: map.core.id,
613
627
  header: map.core.header,
614
628
  new: {},
629
+ priority: getPriorityFromHeader(map.core.header),
615
630
  });
616
631
  await inTx2.push(mapSubscriptionMsg);
617
632
  const mapTellKnownStateMsg = (await outRxQ2.next()).value;
@@ -665,14 +680,14 @@ test("Can sync a coValue through a server to another client", async () => {
665
680
  map.set("hello", "world", "trusting");
666
681
  const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
667
682
  const server = new LocalNode(serverUser, serverSession, Crypto);
668
- const [serverAsPeerForClient1, client1AsPeer] = await connectedPeers("serverFor1", "client1", {
683
+ const [serverAsPeerForClient1, client1AsPeer] = connectedPeers("serverFor1", "client1", {
669
684
  peer1role: "server",
670
685
  peer2role: "client",
671
686
  trace: true,
672
687
  });
673
688
  client1.syncManager.addPeer(serverAsPeerForClient1);
674
689
  server.syncManager.addPeer(client1AsPeer);
675
- const client2 = new LocalNode(admin, Crypto.newRandomSessionID(admin.id), Crypto);
690
+ const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
676
691
  const [serverAsPeerForClient2, client2AsPeer] = connectedPeers("serverFor2", "client2", {
677
692
  peer1role: "server",
678
693
  peer2role: "client",
@@ -694,15 +709,15 @@ test("Can sync a coValue with private transactions through a server to another c
694
709
  map.set("hello", "world", "private");
695
710
  const [serverUser, serverSession] = randomAnonymousAccountAndSessionID();
696
711
  const server = new LocalNode(serverUser, serverSession, Crypto);
697
- const [serverAsPeer, client1AsPeer] = await connectedPeers("server", "client1", {
712
+ const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
698
713
  trace: true,
699
714
  peer1role: "server",
700
715
  peer2role: "client",
701
716
  });
702
717
  client1.syncManager.addPeer(serverAsPeer);
703
718
  server.syncManager.addPeer(client1AsPeer);
704
- const client2 = new LocalNode(admin, client1.crypto.newRandomSessionID(admin.id), Crypto);
705
- const [serverAsOtherPeer, client2AsPeer] = await connectedPeers("server", "client2", {
719
+ const client2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
720
+ const [serverAsOtherPeer, client2AsPeer] = connectedPeers("server", "client2", {
706
721
  trace: true,
707
722
  peer1role: "server",
708
723
  peer2role: "client",
@@ -832,8 +847,8 @@ test("If we start loading a coValue before connecting to a peer that has it, it
832
847
  const group = node1.createGroup();
833
848
  const map = group.createMap();
834
849
  map.set("hello", "world", "trusting");
835
- const node2 = new LocalNode(admin, Crypto.newRandomSessionID(admin.id), Crypto);
836
- const [node1asPeer, node2asPeer] = await connectedPeers("peer1", "peer2", {
850
+ const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
851
+ const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2", {
837
852
  peer1role: "server",
838
853
  peer2role: "client",
839
854
  trace: true,
@@ -848,6 +863,303 @@ test("If we start loading a coValue before connecting to a peer that has it, it
848
863
  }
849
864
  expect(expectMap(mapOnNode2.getCurrentContent()).get("hello")).toEqual("world");
850
865
  });
866
+ describe("sync - extra tests", () => {
867
+ test("Node handles disconnection and reconnection of a peer gracefully", async () => {
868
+ // Create two nodes
869
+ const [admin1, session1] = randomAnonymousAccountAndSessionID();
870
+ const node1 = new LocalNode(admin1, session1, Crypto);
871
+ const [admin2, session2] = randomAnonymousAccountAndSessionID();
872
+ const node2 = new LocalNode(admin2, session2, Crypto);
873
+ // Create a group and a map on node1
874
+ const group = node1.createGroup();
875
+ group.addMember("everyone", "writer");
876
+ const map = group.createMap();
877
+ map.set("key1", "value1", "trusting");
878
+ // Connect the nodes
879
+ const [node1AsPeer, node2AsPeer] = connectedPeers("node1", "node2", {
880
+ peer1role: "server",
881
+ peer2role: "client",
882
+ });
883
+ node1.syncManager.addPeer(node2AsPeer);
884
+ node2.syncManager.addPeer(node1AsPeer);
885
+ // Wait for initial sync
886
+ await new Promise((resolve) => setTimeout(resolve, 100));
887
+ // Verify that node2 has received the map
888
+ const mapOnNode2 = await node2.loadCoValueCore(map.core.id);
889
+ if (mapOnNode2 === "unavailable") {
890
+ throw new Error("Map is unavailable on node2");
891
+ }
892
+ expect(expectMap(mapOnNode2.getCurrentContent()).get("key1")).toEqual("value1");
893
+ // Simulate disconnection
894
+ node1.syncManager.gracefulShutdown();
895
+ node2.syncManager.gracefulShutdown();
896
+ // Make changes on node1 while disconnected
897
+ map.set("key2", "value2", "trusting");
898
+ // Simulate reconnection
899
+ const [newNode1AsPeer, newNode2AsPeer] = connectedPeers("node11", "node22", {
900
+ peer1role: "server",
901
+ peer2role: "client",
902
+ // trace: true,
903
+ });
904
+ node1.syncManager.addPeer(newNode2AsPeer);
905
+ node2.syncManager.addPeer(newNode1AsPeer);
906
+ // Wait for re-sync
907
+ await new Promise((resolve) => setTimeout(resolve, 100));
908
+ // Verify that node2 has received the changes made during disconnection
909
+ const updatedMapOnNode2 = await node2.loadCoValueCore(map.core.id);
910
+ if (updatedMapOnNode2 === "unavailable") {
911
+ throw new Error("Updated map is unavailable on node2");
912
+ }
913
+ expect(expectMap(updatedMapOnNode2.getCurrentContent()).get("key2")).toEqual("value2");
914
+ // Make a new change on node2 to verify two-way sync
915
+ const mapOnNode2ForEdit = await node2.loadCoValueCore(map.core.id);
916
+ if (mapOnNode2ForEdit === "unavailable") {
917
+ throw new Error("Updated map is unavailable on node2");
918
+ }
919
+ const success = mapOnNode2ForEdit.makeTransaction([
920
+ {
921
+ op: "set",
922
+ key: "key3",
923
+ value: "value3",
924
+ },
925
+ ], "trusting");
926
+ if (!success) {
927
+ throw new Error("Failed to make transaction");
928
+ }
929
+ // Wait for sync back to node1
930
+ await new Promise((resolve) => setTimeout(resolve, 100));
931
+ const mapOnNode1 = await node1.loadCoValueCore(map.core.id);
932
+ if (mapOnNode1 === "unavailable") {
933
+ throw new Error("Updated map is unavailable on node1");
934
+ }
935
+ // Verify that node1 has received the change from node2
936
+ expect(expectMap(mapOnNode1.getCurrentContent()).get("key3")).toEqual("value3");
937
+ });
938
+ test("Concurrent modifications on multiple nodes are resolved correctly", async () => {
939
+ // Create three nodes
940
+ const [admin1, session1] = randomAnonymousAccountAndSessionID();
941
+ const node1 = new LocalNode(admin1, session1, Crypto);
942
+ const [admin2, session2] = randomAnonymousAccountAndSessionID();
943
+ const node2 = new LocalNode(admin2, session2, Crypto);
944
+ const [admin3, session3] = randomAnonymousAccountAndSessionID();
945
+ const node3 = new LocalNode(admin3, session3, Crypto);
946
+ // Create a group and a map on node1
947
+ const group = node1.createGroup();
948
+ group.addMember("everyone", "writer");
949
+ const map = group.createMap();
950
+ // Connect the nodes in a triangle topology
951
+ const [node1AsPeerFor2, node2AsPeerFor1] = connectedPeers("node1", "node2", {
952
+ peer1role: "server",
953
+ peer2role: "client",
954
+ // trace: true,
955
+ });
956
+ const [node2AsPeerFor3, node3AsPeerFor2] = connectedPeers("node2", "node3", {
957
+ peer1role: "server",
958
+ peer2role: "client",
959
+ // trace: true,
960
+ });
961
+ const [node3AsPeerFor1, node1AsPeerFor3] = connectedPeers("node3", "node1", {
962
+ peer1role: "server",
963
+ peer2role: "client",
964
+ // trace: true,
965
+ });
966
+ node1.syncManager.addPeer(node2AsPeerFor1);
967
+ node1.syncManager.addPeer(node3AsPeerFor1);
968
+ node2.syncManager.addPeer(node1AsPeerFor2);
969
+ node2.syncManager.addPeer(node3AsPeerFor2);
970
+ node3.syncManager.addPeer(node1AsPeerFor3);
971
+ node3.syncManager.addPeer(node2AsPeerFor3);
972
+ // Wait for initial sync
973
+ await new Promise((resolve) => setTimeout(resolve, 100));
974
+ // Verify that all nodes have the map
975
+ const mapOnNode1 = await node1.loadCoValueCore(map.core.id);
976
+ const mapOnNode2 = await node2.loadCoValueCore(map.core.id);
977
+ const mapOnNode3 = await node3.loadCoValueCore(map.core.id);
978
+ if (mapOnNode1 === "unavailable" ||
979
+ mapOnNode2 === "unavailable" ||
980
+ mapOnNode3 === "unavailable") {
981
+ throw new Error("Map is unavailable on node2 or node3");
982
+ }
983
+ // Perform concurrent modifications
984
+ map.set("key1", "value1", "trusting");
985
+ new RawCoMap(mapOnNode2).set("key2", "value2", "trusting");
986
+ new RawCoMap(mapOnNode3).set("key3", "value3", "trusting");
987
+ // Wait for sync to complete
988
+ await new Promise((resolve) => setTimeout(resolve, 200));
989
+ // Verify that all nodes have the same final state
990
+ const finalStateNode1 = expectMap(mapOnNode1.getCurrentContent());
991
+ const finalStateNode2 = expectMap(mapOnNode2.getCurrentContent());
992
+ const finalStateNode3 = expectMap(mapOnNode3.getCurrentContent());
993
+ const expectedState = {
994
+ key1: "value1",
995
+ key2: "value2",
996
+ key3: "value3",
997
+ };
998
+ expect(finalStateNode1.toJSON()).toEqual(expectedState);
999
+ expect(finalStateNode2.toJSON()).toEqual(expectedState);
1000
+ expect(finalStateNode3.toJSON()).toEqual(expectedState);
1001
+ });
1002
+ test.skip("Large coValues are synced efficiently in chunks", async () => {
1003
+ // Create two nodes
1004
+ const [admin1, session1] = randomAnonymousAccountAndSessionID();
1005
+ const node1 = new LocalNode(admin1, session1, Crypto);
1006
+ const [admin2, session2] = randomAnonymousAccountAndSessionID();
1007
+ const node2 = new LocalNode(admin2, session2, Crypto);
1008
+ // Create a group and a large map on node1
1009
+ const group = node1.createGroup();
1010
+ group.addMember("everyone", "writer");
1011
+ const largeMap = group.createMap();
1012
+ // Generate a large amount of data (about 10MB)
1013
+ const dataSize = 1 * 1024 * 1024;
1014
+ const chunkSize = 1024; // 1KB chunks
1015
+ const chunks = dataSize / chunkSize;
1016
+ for (let i = 0; i < chunks; i++) {
1017
+ const key = `key${i}`;
1018
+ const value = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
1019
+ largeMap.set(key, value, "trusting");
1020
+ }
1021
+ // Connect the nodes
1022
+ const [node1AsPeer, node2AsPeer] = connectedPeers("node1", "node2", {
1023
+ peer1role: "server",
1024
+ peer2role: "client",
1025
+ });
1026
+ node1.syncManager.addPeer(node2AsPeer);
1027
+ node2.syncManager.addPeer(node1AsPeer);
1028
+ await new Promise((resolve) => setTimeout(resolve, 4000));
1029
+ // Measure sync time
1030
+ const startSync = performance.now();
1031
+ // Load the large map on node2
1032
+ const largeMapOnNode2 = await node2.loadCoValueCore(largeMap.core.id);
1033
+ if (largeMapOnNode2 === "unavailable") {
1034
+ throw new Error("Large map is unavailable on node2");
1035
+ }
1036
+ const endSync = performance.now();
1037
+ const syncTime = endSync - startSync;
1038
+ // Verify that all data was synced correctly
1039
+ const syncedMap = new RawCoMap(largeMapOnNode2);
1040
+ expect(Object.keys(largeMapOnNode2.getCurrentContent().toJSON() || {})
1041
+ .length).toBe(chunks);
1042
+ for (let i = 0; i < chunks; i++) {
1043
+ const key = `key${i}`;
1044
+ const expectedValue = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
1045
+ expect(syncedMap.get(key)).toBe(expectedValue);
1046
+ }
1047
+ // Check that sync time is reasonable (this threshold may need adjustment)
1048
+ const reasonableSyncTime = 10; // 30 seconds
1049
+ expect(syncTime).toBeLessThan(reasonableSyncTime);
1050
+ // Check memory usage (this threshold may need adjustment)
1051
+ const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
1052
+ const reasonableMemoryUsage = 1; // 500 MB
1053
+ expect(memoryUsage).toBeLessThan(reasonableMemoryUsage);
1054
+ });
1055
+ test("Node correctly handles and recovers from network partitions", async () => {
1056
+ // Create three nodes
1057
+ const [admin1, session1] = randomAnonymousAccountAndSessionID();
1058
+ const node1 = new LocalNode(admin1, session1, Crypto);
1059
+ const [admin2, session2] = randomAnonymousAccountAndSessionID();
1060
+ const node2 = new LocalNode(admin2, session2, Crypto);
1061
+ const [admin3, session3] = randomAnonymousAccountAndSessionID();
1062
+ const node3 = new LocalNode(admin3, session3, Crypto);
1063
+ // Create a group and a map on node1
1064
+ const group = node1.createGroup();
1065
+ group.addMember("everyone", "writer");
1066
+ const map = group.createMap();
1067
+ map.set("initial", "value", "trusting");
1068
+ // Connect all nodes
1069
+ const [node1AsPeerFor2, node2AsPeerFor1] = connectedPeers("node1", "node2", {
1070
+ peer1role: "server",
1071
+ peer2role: "client",
1072
+ // trace: true,
1073
+ });
1074
+ const [node2AsPeerFor3, node3AsPeerFor2] = connectedPeers("node2", "node3", {
1075
+ peer1role: "server",
1076
+ peer2role: "client",
1077
+ // trace: true,
1078
+ });
1079
+ const [node3AsPeerFor1, node1AsPeerFor3] = connectedPeers("node3", "node1", {
1080
+ peer1role: "server",
1081
+ peer2role: "client",
1082
+ // trace: true,
1083
+ });
1084
+ node1.syncManager.addPeer(node2AsPeerFor1);
1085
+ node1.syncManager.addPeer(node3AsPeerFor1);
1086
+ node2.syncManager.addPeer(node1AsPeerFor2);
1087
+ node2.syncManager.addPeer(node3AsPeerFor2);
1088
+ node3.syncManager.addPeer(node1AsPeerFor3);
1089
+ node3.syncManager.addPeer(node2AsPeerFor3);
1090
+ // Wait for initial sync
1091
+ await new Promise((resolve) => setTimeout(resolve, 100));
1092
+ // Verify initial state
1093
+ const mapOnNode1Core = await node1.loadCoValueCore(map.core.id);
1094
+ const mapOnNode2Core = await node2.loadCoValueCore(map.core.id);
1095
+ const mapOnNode3Core = await node3.loadCoValueCore(map.core.id);
1096
+ if (mapOnNode1Core === "unavailable" ||
1097
+ mapOnNode2Core === "unavailable" ||
1098
+ mapOnNode3Core === "unavailable") {
1099
+ throw new Error("Map is unavailable on node2 or node3");
1100
+ }
1101
+ // const mapOnNode1 = new RawCoMap(mapOnNode1Core);
1102
+ const mapOnNode2 = new RawCoMap(mapOnNode2Core);
1103
+ const mapOnNode3 = new RawCoMap(mapOnNode3Core);
1104
+ expect(mapOnNode2.get("initial")).toBe("value");
1105
+ expect(mapOnNode3.get("initial")).toBe("value");
1106
+ // Simulate network partition: disconnect node3 from node1 and node2
1107
+ node1.syncManager.peers["node3"]?.gracefulShutdown();
1108
+ delete node1.syncManager.peers["node3"];
1109
+ node2.syncManager.peers["node3"]?.gracefulShutdown();
1110
+ delete node2.syncManager.peers["node3"];
1111
+ node3.syncManager.peers["node1"]?.gracefulShutdown();
1112
+ delete node3.syncManager.peers["node1"];
1113
+ node3.syncManager.peers["node2"]?.gracefulShutdown();
1114
+ delete node3.syncManager.peers["node2"];
1115
+ // Make changes on both sides of the partition
1116
+ map.set("node1", "partition", "trusting");
1117
+ mapOnNode2.set("node2", "partition", "trusting");
1118
+ mapOnNode3.set("node3", "partition", "trusting");
1119
+ // Wait for sync between node1 and node2
1120
+ await new Promise((resolve) => setTimeout(resolve, 100));
1121
+ // Verify that node1 and node2 are in sync, but node3 is not
1122
+ expect(expectMap(mapOnNode1Core.getCurrentContent()).get("node1")).toBe("partition");
1123
+ expect(expectMap(mapOnNode1Core.getCurrentContent()).get("node2")).toBe("partition");
1124
+ expect(expectMap(mapOnNode1Core.getCurrentContent()).toJSON()?.node3).toBe(undefined);
1125
+ expect(expectMap(mapOnNode2Core.getCurrentContent()).get("node1")).toBe("partition");
1126
+ expect(expectMap(mapOnNode2Core.getCurrentContent()).get("node2")).toBe("partition");
1127
+ expect(expectMap(mapOnNode2Core.getCurrentContent()).toJSON()?.node3).toBe(undefined);
1128
+ expect(expectMap(mapOnNode3Core.getCurrentContent()).toJSON()?.node1).toBe(undefined);
1129
+ expect(expectMap(mapOnNode3Core.getCurrentContent()).toJSON()?.node2).toBe(undefined);
1130
+ expect(expectMap(mapOnNode3Core.getCurrentContent()).toJSON()?.node3).toBe("partition");
1131
+ // Restore connectivity
1132
+ const [newNode3AsPeerFor1, newNode1AsPeerFor3] = connectedPeers("node3", "node1", {
1133
+ peer1role: "server",
1134
+ peer2role: "client",
1135
+ trace: true,
1136
+ });
1137
+ const [newNode3AsPeerFor2, newNode2AsPeerFor3] = connectedPeers("node3", "node2", {
1138
+ peer1role: "server",
1139
+ peer2role: "client",
1140
+ trace: true,
1141
+ });
1142
+ node1.syncManager.addPeer(newNode3AsPeerFor1);
1143
+ node2.syncManager.addPeer(newNode3AsPeerFor2);
1144
+ node3.syncManager.addPeer(newNode1AsPeerFor3);
1145
+ node3.syncManager.addPeer(newNode2AsPeerFor3);
1146
+ // Wait for re-sync
1147
+ await new Promise((resolve) => setTimeout(resolve, 200));
1148
+ // Verify final state: all nodes should have all changes
1149
+ const finalStateNode1 = expectMap(mapOnNode1Core.getCurrentContent()).toJSON();
1150
+ const finalStateNode2 = expectMap(mapOnNode2Core.getCurrentContent()).toJSON();
1151
+ const finalStateNode3 = expectMap(mapOnNode3Core.getCurrentContent()).toJSON();
1152
+ const expectedFinalState = {
1153
+ initial: "value",
1154
+ node1: "partition",
1155
+ node2: "partition",
1156
+ node3: "partition",
1157
+ };
1158
+ expect(finalStateNode1).toEqual(expectedFinalState);
1159
+ expect(finalStateNode2).toEqual(expectedFinalState);
1160
+ expect(finalStateNode3).toEqual(expectedFinalState);
1161
+ });
1162
+ });
851
1163
  function groupContentEx(group) {
852
1164
  return {
853
1165
  action: "content",