cojson 0.13.16 → 0.13.18

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 (168) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/PeerState.d.ts +3 -0
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +9 -0
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts.map +1 -1
  8. package/dist/SyncStateManager.js +2 -3
  9. package/dist/SyncStateManager.js.map +1 -1
  10. package/dist/coValue.d.ts +6 -4
  11. package/dist/coValue.d.ts.map +1 -1
  12. package/dist/coValue.js +5 -4
  13. package/dist/coValue.js.map +1 -1
  14. package/dist/coValueCore/coValueCore.d.ts +143 -0
  15. package/dist/coValueCore/coValueCore.d.ts.map +1 -0
  16. package/dist/{coValueCore.js → coValueCore/coValueCore.js} +314 -246
  17. package/dist/coValueCore/coValueCore.js.map +1 -0
  18. package/dist/coValueCore/verifiedState.d.ts +65 -0
  19. package/dist/coValueCore/verifiedState.d.ts.map +1 -0
  20. package/dist/coValueCore/verifiedState.js +210 -0
  21. package/dist/coValueCore/verifiedState.js.map +1 -0
  22. package/dist/coValues/account.d.ts +8 -10
  23. package/dist/coValues/account.d.ts.map +1 -1
  24. package/dist/coValues/account.js +12 -13
  25. package/dist/coValues/account.js.map +1 -1
  26. package/dist/coValues/coList.d.ts +10 -6
  27. package/dist/coValues/coList.d.ts.map +1 -1
  28. package/dist/coValues/coList.js +41 -15
  29. package/dist/coValues/coList.js.map +1 -1
  30. package/dist/coValues/coMap.d.ts +4 -3
  31. package/dist/coValues/coMap.d.ts.map +1 -1
  32. package/dist/coValues/coMap.js +5 -3
  33. package/dist/coValues/coMap.js.map +1 -1
  34. package/dist/coValues/coPlainText.d.ts +2 -2
  35. package/dist/coValues/coPlainText.d.ts.map +1 -1
  36. package/dist/coValues/coPlainText.js +5 -5
  37. package/dist/coValues/coPlainText.js.map +1 -1
  38. package/dist/coValues/coStream.d.ts +5 -4
  39. package/dist/coValues/coStream.d.ts.map +1 -1
  40. package/dist/coValues/coStream.js +5 -3
  41. package/dist/coValues/coStream.js.map +1 -1
  42. package/dist/coValues/group.d.ts +7 -2
  43. package/dist/coValues/group.d.ts.map +1 -1
  44. package/dist/coValues/group.js +29 -26
  45. package/dist/coValues/group.js.map +1 -1
  46. package/dist/coreToCoValue.d.ts +4 -3
  47. package/dist/coreToCoValue.d.ts.map +1 -1
  48. package/dist/coreToCoValue.js +10 -14
  49. package/dist/coreToCoValue.js.map +1 -1
  50. package/dist/exports.d.ts +6 -5
  51. package/dist/exports.d.ts.map +1 -1
  52. package/dist/exports.js +3 -4
  53. package/dist/exports.js.map +1 -1
  54. package/dist/localNode.d.ts +30 -24
  55. package/dist/localNode.d.ts.map +1 -1
  56. package/dist/localNode.js +153 -177
  57. package/dist/localNode.js.map +1 -1
  58. package/dist/permissions.d.ts +2 -1
  59. package/dist/permissions.d.ts.map +1 -1
  60. package/dist/permissions.js +15 -11
  61. package/dist/permissions.js.map +1 -1
  62. package/dist/priority.d.ts +1 -1
  63. package/dist/priority.d.ts.map +1 -1
  64. package/dist/sync.d.ts +2 -2
  65. package/dist/sync.d.ts.map +1 -1
  66. package/dist/sync.js +86 -55
  67. package/dist/sync.js.map +1 -1
  68. package/dist/tests/coList.test.js +133 -13
  69. package/dist/tests/coList.test.js.map +1 -1
  70. package/dist/tests/coMap.test.js +43 -14
  71. package/dist/tests/coMap.test.js.map +1 -1
  72. package/dist/tests/coPlainText.test.js +9 -10
  73. package/dist/tests/coPlainText.test.js.map +1 -1
  74. package/dist/tests/coStream.test.js +49 -18
  75. package/dist/tests/coStream.test.js.map +1 -1
  76. package/dist/tests/coValueCore.test.js +22 -28
  77. package/dist/tests/coValueCore.test.js.map +1 -1
  78. package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
  79. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
  80. package/dist/tests/coValueCoreLoadingState.test.js +227 -0
  81. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
  82. package/dist/tests/group.test.js +42 -43
  83. package/dist/tests/group.test.js.map +1 -1
  84. package/dist/tests/messagesTestUtils.d.ts +2 -2
  85. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  86. package/dist/tests/messagesTestUtils.js +1 -1
  87. package/dist/tests/messagesTestUtils.js.map +1 -1
  88. package/dist/tests/permissions.test.js +224 -292
  89. package/dist/tests/permissions.test.js.map +1 -1
  90. package/dist/tests/priority.test.js +13 -14
  91. package/dist/tests/priority.test.js.map +1 -1
  92. package/dist/tests/sync.auth.test.d.ts +2 -0
  93. package/dist/tests/sync.auth.test.d.ts.map +1 -0
  94. package/dist/tests/sync.auth.test.js +141 -0
  95. package/dist/tests/sync.auth.test.js.map +1 -0
  96. package/dist/tests/sync.load.test.js +60 -2
  97. package/dist/tests/sync.load.test.js.map +1 -1
  98. package/dist/tests/sync.mesh.test.js +70 -10
  99. package/dist/tests/sync.mesh.test.js.map +1 -1
  100. package/dist/tests/sync.peerReconciliation.test.js +19 -19
  101. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  102. package/dist/tests/sync.storage.test.js +20 -13
  103. package/dist/tests/sync.storage.test.js.map +1 -1
  104. package/dist/tests/sync.test.js +32 -39
  105. package/dist/tests/sync.test.js.map +1 -1
  106. package/dist/tests/sync.upload.test.js +126 -37
  107. package/dist/tests/sync.upload.test.js.map +1 -1
  108. package/dist/tests/testUtils.d.ts +24 -15
  109. package/dist/tests/testUtils.d.ts.map +1 -1
  110. package/dist/tests/testUtils.js +88 -61
  111. package/dist/tests/testUtils.js.map +1 -1
  112. package/dist/typeUtils/expectGroup.js +1 -1
  113. package/dist/typeUtils/expectGroup.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/PeerState.ts +11 -0
  116. package/src/SyncStateManager.ts +2 -3
  117. package/src/coValue.ts +14 -8
  118. package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +470 -413
  119. package/src/coValueCore/verifiedState.ts +376 -0
  120. package/src/coValues/account.ts +20 -25
  121. package/src/coValues/coList.ts +63 -29
  122. package/src/coValues/coMap.ts +13 -6
  123. package/src/coValues/coPlainText.ts +10 -8
  124. package/src/coValues/coStream.ts +12 -7
  125. package/src/coValues/group.ts +50 -28
  126. package/src/coreToCoValue.ts +14 -15
  127. package/src/exports.ts +9 -7
  128. package/src/localNode.ts +248 -283
  129. package/src/permissions.ts +18 -12
  130. package/src/priority.ts +1 -1
  131. package/src/sync.ts +96 -63
  132. package/src/tests/coList.test.ts +200 -12
  133. package/src/tests/coMap.test.ts +65 -14
  134. package/src/tests/coPlainText.test.ts +12 -9
  135. package/src/tests/coStream.test.ts +80 -17
  136. package/src/tests/coValueCore.test.ts +30 -27
  137. package/src/tests/coValueCoreLoadingState.test.ts +337 -0
  138. package/src/tests/group.test.ts +44 -68
  139. package/src/tests/messagesTestUtils.ts +3 -8
  140. package/src/tests/permissions.test.ts +283 -449
  141. package/src/tests/priority.test.ts +17 -13
  142. package/src/tests/sync.auth.test.ts +188 -0
  143. package/src/tests/sync.load.test.ts +79 -2
  144. package/src/tests/sync.mesh.test.ts +89 -9
  145. package/src/tests/sync.peerReconciliation.test.ts +25 -25
  146. package/src/tests/sync.storage.test.ts +20 -13
  147. package/src/tests/sync.test.ts +43 -43
  148. package/src/tests/sync.upload.test.ts +157 -37
  149. package/src/tests/testUtils.ts +120 -74
  150. package/src/typeUtils/expectGroup.ts +1 -1
  151. package/dist/CoValuesStore.d.ts +0 -14
  152. package/dist/CoValuesStore.d.ts.map +0 -1
  153. package/dist/CoValuesStore.js +0 -32
  154. package/dist/CoValuesStore.js.map +0 -1
  155. package/dist/coValueCore.d.ts +0 -141
  156. package/dist/coValueCore.d.ts.map +0 -1
  157. package/dist/coValueCore.js.map +0 -1
  158. package/dist/coValueState.d.ts +0 -34
  159. package/dist/coValueState.d.ts.map +0 -1
  160. package/dist/coValueState.js +0 -228
  161. package/dist/coValueState.js.map +0 -1
  162. package/dist/tests/coValueState.test.d.ts +0 -2
  163. package/dist/tests/coValueState.test.d.ts.map +0 -1
  164. package/dist/tests/coValueState.test.js +0 -344
  165. package/dist/tests/coValueState.test.js.map +0 -1
  166. package/src/CoValuesStore.ts +0 -41
  167. package/src/coValueState.ts +0 -300
  168. package/src/tests/coValueState.test.ts +0 -525
@@ -2,7 +2,11 @@ import { describe, expect, test } from "vitest";
2
2
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
3
3
  import { LocalNode } from "../localNode.js";
4
4
  import { CO_VALUE_PRIORITY, getPriorityFromHeader } from "../priority.js";
5
- import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
5
+ import {
6
+ createAccountInNode,
7
+ nodeWithRandomAgentAndSessionID,
8
+ randomAgentAndSessionID,
9
+ } from "./testUtils.js";
6
10
 
7
11
  const Crypto = await WasmCrypto.create();
8
12
 
@@ -14,7 +18,7 @@ describe("getPriorityFromHeader", () => {
14
18
  });
15
19
 
16
20
  test("returns MEDIUM priority for costream type", () => {
17
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
21
+ const node = nodeWithRandomAgentAndSessionID();
18
22
  const costream = node.createCoValue({
19
23
  type: "costream",
20
24
  ruleset: { type: "unsafeAllowAll" },
@@ -22,13 +26,13 @@ describe("getPriorityFromHeader", () => {
22
26
  ...Crypto.createdNowUnique(),
23
27
  });
24
28
 
25
- expect(getPriorityFromHeader(costream.header)).toEqual(
29
+ expect(getPriorityFromHeader(costream.verified.header)).toEqual(
26
30
  CO_VALUE_PRIORITY.MEDIUM,
27
31
  );
28
32
  });
29
33
 
30
34
  test("returns LOW priority for binary costream type", () => {
31
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
35
+ const node = nodeWithRandomAgentAndSessionID();
32
36
  const costream = node.createCoValue({
33
37
  type: "costream",
34
38
  ruleset: { type: "unsafeAllowAll" },
@@ -36,32 +40,32 @@ describe("getPriorityFromHeader", () => {
36
40
  ...Crypto.createdNowUnique(),
37
41
  });
38
42
 
39
- expect(getPriorityFromHeader(costream.header)).toEqual(
43
+ expect(getPriorityFromHeader(costream.verified.header)).toEqual(
40
44
  CO_VALUE_PRIORITY.LOW,
41
45
  );
42
46
  });
43
47
 
44
48
  test("returns HIGH priority for account type", async () => {
45
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
49
+ const node = nodeWithRandomAgentAndSessionID();
46
50
 
47
- const account = node.createAccount(node.crypto.newRandomAgentSecret());
51
+ const account = createAccountInNode(node);
48
52
 
49
- expect(getPriorityFromHeader(account.core.header)).toEqual(
53
+ expect(getPriorityFromHeader(account.account.core.verified.header)).toEqual(
50
54
  CO_VALUE_PRIORITY.HIGH,
51
55
  );
52
56
  });
53
57
 
54
58
  test("returns HIGH priority for group type", () => {
55
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
59
+ const node = nodeWithRandomAgentAndSessionID();
56
60
  const group = node.createGroup();
57
61
 
58
- expect(getPriorityFromHeader(group.core.header)).toEqual(
62
+ expect(getPriorityFromHeader(group.core.verified.header)).toEqual(
59
63
  CO_VALUE_PRIORITY.HIGH,
60
64
  );
61
65
  });
62
66
 
63
67
  test("returns MEDIUM priority for other types", () => {
64
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
68
+ const node = nodeWithRandomAgentAndSessionID();
65
69
 
66
70
  const comap = node.createCoValue({
67
71
  type: "comap",
@@ -77,10 +81,10 @@ describe("getPriorityFromHeader", () => {
77
81
  ...Crypto.createdNowUnique(),
78
82
  });
79
83
 
80
- expect(getPriorityFromHeader(comap.header)).toEqual(
84
+ expect(getPriorityFromHeader(comap.verified.header)).toEqual(
81
85
  CO_VALUE_PRIORITY.MEDIUM,
82
86
  );
83
- expect(getPriorityFromHeader(colist.header)).toEqual(
87
+ expect(getPriorityFromHeader(colist.verified.header)).toEqual(
84
88
  CO_VALUE_PRIORITY.MEDIUM,
85
89
  );
86
90
  });
@@ -0,0 +1,188 @@
1
+ import { assert, beforeEach, describe, expect, test } from "vitest";
2
+
3
+ import { expectMap } from "../coValue";
4
+ import { expectAccount } from "../coValues/account";
5
+ import { WasmCrypto } from "../crypto/WasmCrypto";
6
+ import { LocalNode } from "../localNode";
7
+ import { toSimplifiedMessages } from "./messagesTestUtils";
8
+ import {
9
+ SyncMessagesLog,
10
+ getSyncServerConnectedPeer,
11
+ loadCoValueOrFail,
12
+ setupTestNode,
13
+ waitFor,
14
+ } from "./testUtils";
15
+
16
+ const Crypto = await WasmCrypto.create();
17
+ let jazzCloud = setupTestNode({ isSyncServer: true });
18
+
19
+ beforeEach(async () => {
20
+ SyncMessagesLog.clear();
21
+ jazzCloud = setupTestNode({ isSyncServer: true });
22
+ });
23
+
24
+ describe("LocalNode auth sync", () => {
25
+ test("create a new account", async () => {
26
+ const { peer } = getSyncServerConnectedPeer({
27
+ peerId: "new-account",
28
+ });
29
+
30
+ const { accountID, node } = await LocalNode.withNewlyCreatedAccount({
31
+ creationProps: {
32
+ name: "new-account",
33
+ },
34
+ peersToLoadFrom: [peer],
35
+ crypto: Crypto,
36
+ });
37
+
38
+ const account = node.expectCurrentAccount("after login");
39
+ await account.core.waitForSync();
40
+
41
+ const profileID = account.get("profile")!;
42
+
43
+ const profileCoreOnSyncServer = jazzCloud.node.getCoValue(profileID);
44
+
45
+ expect(profileCoreOnSyncServer.isAvailable()).toBe(true);
46
+
47
+ assert(profileCoreOnSyncServer.isAvailable());
48
+
49
+ expect(
50
+ SyncMessagesLog.getMessages({
51
+ Account: account.core,
52
+ Profile: profileCoreOnSyncServer,
53
+ ProfileGroup: profileCoreOnSyncServer.getGroup().core,
54
+ }),
55
+ ).toMatchInlineSnapshot(`
56
+ [
57
+ "client -> server | CONTENT Account header: true new: After: 0 New: 4",
58
+ "server -> client | KNOWN Account sessions: header/4",
59
+ "client -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
60
+ "server -> client | KNOWN ProfileGroup sessions: header/5",
61
+ "client -> server | CONTENT Profile header: true new: After: 0 New: 1",
62
+ "server -> client | KNOWN Profile sessions: header/1",
63
+ ]
64
+ `);
65
+ });
66
+
67
+ test("create a new account with a migration", async () => {
68
+ const { peer } = getSyncServerConnectedPeer({
69
+ peerId: "new-account",
70
+ });
71
+
72
+ const { node } = await LocalNode.withNewlyCreatedAccount({
73
+ creationProps: {
74
+ name: "new-account",
75
+ },
76
+ peersToLoadFrom: [peer],
77
+ crypto: Crypto,
78
+ async migration(account) {
79
+ const root = account.createMap();
80
+ const profile = account.createMap();
81
+
82
+ root.set("hello", "world");
83
+ profile.set("name", "new-account");
84
+
85
+ account.set("root", root.id);
86
+ account.set("profile", profile.id);
87
+
88
+ await root.core.waitForSync();
89
+ await profile.core.waitForSync();
90
+ },
91
+ });
92
+
93
+ const account = node.expectCurrentAccount("after login");
94
+ await account.core.waitForSync();
95
+
96
+ const rootID = account.get("root")!;
97
+ const profileID = account.get("profile")!;
98
+
99
+ const rootCoreOnSyncServer = jazzCloud.node.getCoValue(rootID);
100
+ expect(rootCoreOnSyncServer.isAvailable()).toBe(true);
101
+
102
+ const profileCoreOnSyncServer = jazzCloud.node.getCoValue(profileID);
103
+
104
+ expect(profileCoreOnSyncServer.isAvailable()).toBe(true);
105
+
106
+ assert(profileCoreOnSyncServer.isAvailable());
107
+ assert(rootCoreOnSyncServer.isAvailable());
108
+
109
+ expect(
110
+ SyncMessagesLog.getMessages({
111
+ Account: account.core,
112
+ Root: rootCoreOnSyncServer,
113
+ Profile: profileCoreOnSyncServer,
114
+ ProfileGroup: profileCoreOnSyncServer.getGroup().core,
115
+ }),
116
+ ).toMatchInlineSnapshot(`
117
+ [
118
+ "client -> server | CONTENT Account header: true new: After: 0 New: 5",
119
+ "server -> client | KNOWN Account sessions: header/5",
120
+ "client -> server | CONTENT Root header: true new: After: 0 New: 1",
121
+ "server -> client | KNOWN Root sessions: header/1",
122
+ "client -> server | CONTENT Profile header: true new: After: 0 New: 1",
123
+ "server -> client | KNOWN Profile sessions: header/1",
124
+ ]
125
+ `);
126
+ });
127
+
128
+ test("authenticate to an existing account", async () => {
129
+ const { peer: newAccountPeer } = getSyncServerConnectedPeer({
130
+ peerId: "new-account",
131
+ });
132
+
133
+ const { accountID, accountSecret } =
134
+ await LocalNode.withNewlyCreatedAccount({
135
+ creationProps: {
136
+ name: "new-account",
137
+ },
138
+ peersToLoadFrom: [newAccountPeer],
139
+ crypto: Crypto,
140
+ });
141
+
142
+ const { peer: existingAccountPeer } = getSyncServerConnectedPeer({
143
+ peerId: "existing-account",
144
+ });
145
+
146
+ SyncMessagesLog.clear();
147
+
148
+ const node = await LocalNode.withLoadedAccount({
149
+ accountID,
150
+ accountSecret,
151
+ peersToLoadFrom: [existingAccountPeer],
152
+ sessionID: undefined,
153
+ crypto: Crypto,
154
+ });
155
+
156
+ const account = node.expectCurrentAccount("after login");
157
+ const profile = node.getCoValue(account.get("profile")!);
158
+
159
+ assert(profile.isAvailable());
160
+
161
+ expect(account.id).toBe(accountID);
162
+ expect(node.agentSecret).toBe(accountSecret);
163
+
164
+ expect(
165
+ SyncMessagesLog.getMessages({
166
+ Account: account.core,
167
+ Profile: profile,
168
+ ProfileGroup: profile.getGroup().core,
169
+ }),
170
+ ).toMatchInlineSnapshot(`
171
+ [
172
+ "client -> server | LOAD Account sessions: empty",
173
+ "server -> client | KNOWN Account sessions: header/4",
174
+ "client -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
175
+ "server -> client | CONTENT Account header: true new: After: 0 New: 4",
176
+ "server -> client | KNOWN ProfileGroup sessions: header/5",
177
+ "client -> server | CONTENT Profile header: true new: After: 0 New: 1",
178
+ "client -> server | KNOWN Account sessions: header/4",
179
+ "server -> client | KNOWN Profile sessions: header/1",
180
+ "client -> server | LOAD Profile sessions: empty",
181
+ "server -> client | CONTENT ProfileGroup header: true new: After: 0 New: 5",
182
+ "client -> server | KNOWN ProfileGroup sessions: header/5",
183
+ "server -> client | CONTENT Profile header: true new: After: 0 New: 1",
184
+ "client -> server | KNOWN Profile sessions: header/1",
185
+ ]
186
+ `);
187
+ });
188
+ });
@@ -45,6 +45,53 @@ describe("loading coValues from server", () => {
45
45
  `);
46
46
  });
47
47
 
48
+ test("unavailable coValue retry", async () => {
49
+ const client = setupTestNode();
50
+ const client2 = setupTestNode();
51
+
52
+ client2.connectToSyncServer({
53
+ ourName: "client2",
54
+ });
55
+
56
+ const group = client.node.createGroup();
57
+ const map = group.createMap();
58
+ map.set("hello", "world", "trusting");
59
+
60
+ const promise = loadCoValueOrFail(client2.node, map.id);
61
+
62
+ await new Promise((resolve) => setTimeout(resolve, 1));
63
+
64
+ client.connectToSyncServer();
65
+
66
+ const mapOnClient2 = await promise;
67
+
68
+ expect(mapOnClient2.get("hello")).toEqual("world");
69
+
70
+ expect(
71
+ SyncMessagesLog.getMessages({
72
+ Group: group.core,
73
+ Map: map.core,
74
+ }),
75
+ ).toMatchInlineSnapshot(`
76
+ [
77
+ "client2 -> server | LOAD Map sessions: empty",
78
+ "server -> client2 | KNOWN Map sessions: empty",
79
+ "client -> server | LOAD Group sessions: header/3",
80
+ "server -> client | KNOWN Group sessions: empty",
81
+ "client -> server | LOAD Map sessions: header/1",
82
+ "server -> client | KNOWN Map sessions: empty",
83
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
84
+ "server -> client | KNOWN Group sessions: header/3",
85
+ "client -> server | CONTENT Map header: true new: After: 0 New: 1",
86
+ "server -> client | KNOWN Map sessions: header/1",
87
+ "server -> client2 | CONTENT Group header: true new: After: 0 New: 3",
88
+ "client2 -> server | KNOWN Group sessions: header/3",
89
+ "server -> client2 | CONTENT Map header: true new: After: 0 New: 1",
90
+ "client2 -> server | KNOWN Map sessions: header/1",
91
+ ]
92
+ `);
93
+ });
94
+
48
95
  test("coValue with parent groups loading", async () => {
49
96
  const client = setupTestNode({
50
97
  connected: true,
@@ -189,7 +236,7 @@ describe("loading coValues from server", () => {
189
236
  await loadCoValueOrFail(client.node, map.id);
190
237
 
191
238
  // Forcefully delete the coValue from the client (simulating some data loss)
192
- client.node.coValuesStore.coValues.delete(map.id);
239
+ client.node.internalDeleteCoValue(map.id);
193
240
 
194
241
  map.set("fromServer", "updated", "trusting");
195
242
 
@@ -327,5 +374,35 @@ describe("loading coValues from server", () => {
327
374
  `);
328
375
  });
329
376
 
330
- test.todo("should mark the coValue as unavailable if the peer is closed");
377
+ test("should mark the coValue as unavailable if the peer is closed", async () => {
378
+ const client = setupTestNode();
379
+ const { peerState } = client.connectToSyncServer();
380
+
381
+ const group = jazzCloud.node.createGroup();
382
+ group.addMember("everyone", "writer");
383
+
384
+ const map = group.createMap({
385
+ test: "value",
386
+ });
387
+
388
+ const promise = client.node.load(map.id);
389
+
390
+ // Close the peer connection
391
+ peerState.gracefulShutdown();
392
+
393
+ expect(await promise).toEqual("unavailable");
394
+
395
+ expect(
396
+ SyncMessagesLog.getMessages({
397
+ Group: group.core,
398
+ Map: map.core,
399
+ }),
400
+ ).toMatchInlineSnapshot(`
401
+ [
402
+ "client -> server | LOAD Map sessions: empty",
403
+ "server -> client | CONTENT Group header: true new: After: 0 New: 5",
404
+ "server -> client | CONTENT Map header: true new: After: 0 New: 1",
405
+ ]
406
+ `);
407
+ });
331
408
  });
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
3
3
  import { expectMap } from "../coValue";
4
4
  import {
5
5
  SyncMessagesLog,
6
+ blockMessageTypeOnOutgoingPeer,
6
7
  loadCoValueOrFail,
7
8
  setupTestNode,
8
9
  waitFor,
@@ -66,9 +67,13 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
66
67
  [
67
68
  "edge-france -> core | CONTENT Group header: true new: After: 0 New: 3",
68
69
  "core -> edge-france | KNOWN Group sessions: header/3",
70
+ "core -> storage | LOAD Group sessions: header/3",
69
71
  "edge-france -> core | CONTENT Map header: true new: After: 0 New: 1",
70
- "core -> storage | CONTENT Group header: true new: After: 0 New: 3",
72
+ "storage -> core | KNOWN Group sessions: empty",
71
73
  "core -> edge-france | KNOWN Map sessions: header/1",
74
+ "core -> storage | LOAD Map sessions: header/1",
75
+ "storage -> core | KNOWN Map sessions: empty",
76
+ "core -> storage | CONTENT Group header: true new: After: 0 New: 3",
72
77
  "storage -> core | KNOWN Group sessions: header/3",
73
78
  "core -> storage | CONTENT Map header: true new: After: 0 New: 1",
74
79
  "storage -> core | KNOWN Map sessions: header/1",
@@ -118,13 +123,19 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
118
123
  [
119
124
  "edge-france -> core | CONTENT ParentGroup header: true new: After: 0 New: 6",
120
125
  "core -> edge-france | KNOWN ParentGroup sessions: header/6",
126
+ "core -> storage | LOAD ParentGroup sessions: header/6",
121
127
  "edge-france -> core | CONTENT Group header: true new: After: 0 New: 5",
122
- "core -> storage | CONTENT ParentGroup header: true new: After: 0 New: 6",
128
+ "storage -> core | KNOWN ParentGroup sessions: empty",
123
129
  "core -> edge-france | KNOWN Group sessions: header/5",
130
+ "core -> storage | LOAD Group sessions: header/5",
124
131
  "edge-france -> core | CONTENT Map header: true new: After: 0 New: 1",
132
+ "storage -> core | KNOWN Group sessions: empty",
133
+ "core -> storage | CONTENT ParentGroup header: true new: After: 0 New: 6",
134
+ "core -> edge-france | KNOWN Map sessions: header/1",
125
135
  "storage -> core | KNOWN ParentGroup sessions: header/6",
136
+ "core -> storage | LOAD Map sessions: header/1",
137
+ "storage -> core | KNOWN Map sessions: empty",
126
138
  "core -> storage | CONTENT Group header: true new: After: 0 New: 5",
127
- "core -> edge-france | KNOWN Map sessions: header/1",
128
139
  "storage -> core | KNOWN Group sessions: header/5",
129
140
  "core -> storage | CONTENT Map header: true new: After: 0 New: 1",
130
141
  "storage -> core | KNOWN Map sessions: header/1",
@@ -214,7 +225,7 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
214
225
  );
215
226
 
216
227
  // Forcefully delete the coValue from the edge (simulating some data loss)
217
- mesh.edgeItaly.node.coValuesStore.coValues.delete(map.id);
228
+ mesh.edgeItaly.node.internalDeleteCoValue(map.id);
218
229
 
219
230
  mapOnClient.set("fromClient", "updated", "trusting");
220
231
  mapOnCoreServer.set("fromServer", "updated", "trusting");
@@ -238,12 +249,16 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
238
249
  "edge-italy -> core | CONTENT Group header: true new: After: 0 New: 5",
239
250
  "edge-italy -> client | CONTENT Group header: true new: After: 0 New: 5",
240
251
  "core -> edge-italy | KNOWN Group sessions: header/5",
252
+ "core -> storage | LOAD Group sessions: header/5",
241
253
  "edge-italy -> core | CONTENT Map header: true new: After: 0 New: 1",
242
254
  "client -> edge-italy | KNOWN Group sessions: header/5",
243
255
  "edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
244
- "core -> storage | CONTENT Group header: true new: After: 0 New: 5",
245
- "core -> edge-italy | KNOWN Map sessions: header/1",
256
+ "storage -> core | KNOWN Group sessions: empty",
257
+ "core -> edge-italy | KNOWN Map sessions: header/3",
258
+ "core -> storage | LOAD Map sessions: header/3",
246
259
  "client -> edge-italy | KNOWN Map sessions: header/1",
260
+ "storage -> core | KNOWN Map sessions: empty",
261
+ "core -> storage | CONTENT Group header: true new: After: 0 New: 5",
247
262
  "storage -> core | KNOWN Group sessions: header/5",
248
263
  "core -> storage | CONTENT Map header: true new: After: 0 New: 1",
249
264
  "storage -> core | KNOWN Map sessions: header/1",
@@ -256,12 +271,12 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
256
271
  "client -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
257
272
  "core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
258
273
  "edge-italy -> client | KNOWN Map sessions: header/2",
259
- "edge-italy -> core | KNOWN Map sessions: header/3",
260
- "edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
261
274
  "edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
262
- "client -> edge-italy | KNOWN Map sessions: header/3",
275
+ "edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
263
276
  "core -> edge-italy | KNOWN Map sessions: header/3",
264
277
  "core -> storage | CONTENT Map header: false new: After: 0 New: 1",
278
+ "edge-italy -> core | KNOWN Map sessions: header/3",
279
+ "client -> edge-italy | KNOWN Map sessions: header/3",
265
280
  "storage -> core | KNOWN Map sessions: header/3",
266
281
  ]
267
282
  `);
@@ -322,4 +337,69 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
322
337
  ]
323
338
  `);
324
339
  });
340
+
341
+ test("load returns the coValue as soon as one of the peers return the content", async () => {
342
+ const client = setupTestNode();
343
+ const coreServer = setupTestNode({
344
+ isSyncServer: true,
345
+ });
346
+
347
+ const { peerOnServer } = client.connectToSyncServer({
348
+ syncServerName: "core",
349
+ });
350
+
351
+ const storage = setupTestNode();
352
+
353
+ const { peer: storagePeer } = client.connectToSyncServer({
354
+ syncServerName: "storage",
355
+ syncServer: storage.node,
356
+ });
357
+
358
+ storagePeer.priority = 100;
359
+
360
+ const group = coreServer.node.createGroup();
361
+ const map = group.createMap();
362
+
363
+ map.set("hello", "world", "trusting");
364
+
365
+ const { peerState } = storage.connectToSyncServer({
366
+ ourName: "storage-of-client",
367
+ syncServerName: "core",
368
+ });
369
+
370
+ await loadCoValueOrFail(storage.node, map.id);
371
+
372
+ peerState.gracefulShutdown();
373
+
374
+ SyncMessagesLog.clear();
375
+
376
+ await new Promise((resolve) => setTimeout(resolve, 100));
377
+
378
+ map.set("hello", "updated", "trusting");
379
+
380
+ // Block the content message from the core peer to simulate the delay on response
381
+ blockMessageTypeOnOutgoingPeer(peerOnServer, "content");
382
+
383
+ const mapOnClient = await loadCoValueOrFail(client.node, map.id);
384
+
385
+ expect(
386
+ SyncMessagesLog.getMessages({
387
+ Group: group.core,
388
+ Map: map.core,
389
+ }),
390
+ ).toMatchInlineSnapshot(`
391
+ [
392
+ "client -> storage | LOAD Map sessions: empty",
393
+ "storage -> client | CONTENT Group header: true new: After: 0 New: 3",
394
+ "client -> storage | KNOWN Group sessions: header/3",
395
+ "client -> core | LOAD Group sessions: header/3",
396
+ "storage -> client | CONTENT Map header: true new: After: 0 New: 1",
397
+ "core -> client | KNOWN Group sessions: header/3",
398
+ "client -> storage | KNOWN Map sessions: header/1",
399
+ "client -> core | LOAD Map sessions: header/1",
400
+ ]
401
+ `);
402
+
403
+ expect(mapOnClient.get("hello")).toEqual("world");
404
+ });
325
405
  });
@@ -31,12 +31,12 @@ describe("peer reconciliation", () => {
31
31
  ).toMatchInlineSnapshot(`
32
32
  [
33
33
  "client -> server | LOAD Group sessions: header/3",
34
- "client -> server | LOAD Map sessions: header/1",
35
34
  "server -> client | KNOWN Group sessions: empty",
36
- "client -> server | CONTENT Group header: true new: After: 0 New: 3",
35
+ "client -> server | LOAD Map sessions: header/1",
37
36
  "server -> client | KNOWN Map sessions: empty",
38
- "client -> server | CONTENT Map header: true new: After: 0 New: 1",
37
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
39
38
  "server -> client | KNOWN Group sessions: header/3",
39
+ "client -> server | CONTENT Map header: true new: After: 0 New: 1",
40
40
  "server -> client | KNOWN Map sessions: header/1",
41
41
  ]
42
42
  `);
@@ -64,13 +64,13 @@ describe("peer reconciliation", () => {
64
64
 
65
65
  await map.core.waitForSync();
66
66
 
67
- const mapOnSyncServer = jazzCloud.node.coValuesStore.get(map.id);
67
+ const mapOnSyncServer = jazzCloud.node.getCoValue(map.id);
68
68
 
69
69
  assert(mapOnSyncServer.isAvailable());
70
70
 
71
- expect(
72
- expectMap(mapOnSyncServer.core.getCurrentContent()).get("hello"),
73
- ).toEqual("updated");
71
+ expect(expectMap(mapOnSyncServer.getCurrentContent()).get("hello")).toEqual(
72
+ "updated",
73
+ );
74
74
 
75
75
  expect(
76
76
  SyncMessagesLog.getMessages({
@@ -80,10 +80,10 @@ describe("peer reconciliation", () => {
80
80
  ).toMatchInlineSnapshot(`
81
81
  [
82
82
  "client -> server | LOAD Group sessions: header/3",
83
- "client -> server | LOAD Map sessions: header/2",
84
83
  "server -> client | KNOWN Group sessions: header/3",
85
- "client -> server | CONTENT Map header: false new: After: 1 New: 1",
84
+ "client -> server | LOAD Map sessions: header/2",
86
85
  "server -> client | KNOWN Map sessions: header/1",
86
+ "client -> server | CONTENT Map header: false new: After: 1 New: 1",
87
87
  "server -> client | KNOWN Map sessions: header/2",
88
88
  ]
89
89
  `);
@@ -111,13 +111,13 @@ describe("peer reconciliation", () => {
111
111
 
112
112
  await map.core.waitForSync();
113
113
 
114
- const mapOnSyncServer = jazzCloud.node.coValuesStore.get(map.id);
114
+ const mapOnSyncServer = jazzCloud.node.getCoValue(map.id);
115
115
 
116
116
  assert(mapOnSyncServer.isAvailable());
117
117
 
118
- expect(
119
- expectMap(mapOnSyncServer.core.getCurrentContent()).get("hello"),
120
- ).toEqual("updated");
118
+ expect(expectMap(mapOnSyncServer.getCurrentContent()).get("hello")).toEqual(
119
+ "updated",
120
+ );
121
121
 
122
122
  expect(peer.outgoing).toMatchObject({
123
123
  closed: true,
@@ -136,10 +136,10 @@ describe("peer reconciliation", () => {
136
136
  [
137
137
  "client -> server | LOAD Group sessions: header/3",
138
138
  "client -> server | LOAD Group sessions: header/3",
139
- "client -> server | LOAD Map sessions: header/2",
140
139
  "server -> client | KNOWN Group sessions: header/3",
141
- "client -> server | CONTENT Map header: false new: After: 1 New: 1",
140
+ "client -> server | LOAD Map sessions: header/2",
142
141
  "server -> client | KNOWN Map sessions: header/1",
142
+ "client -> server | CONTENT Map header: false new: After: 1 New: 1",
143
143
  "server -> client | KNOWN Map sessions: header/2",
144
144
  ]
145
145
  `);
@@ -166,9 +166,9 @@ describe("peer reconciliation", () => {
166
166
  client.connectToSyncServer();
167
167
 
168
168
  await waitFor(() => {
169
- const mapOnSyncServer = jazzCloud.node.coValuesStore.get(map.id);
169
+ const mapOnSyncServer = jazzCloud.node.getCoValue(map.id);
170
170
 
171
- expect(mapOnSyncServer.highLevelState).toBe("available");
171
+ expect(mapOnSyncServer.loadingState).toBe("available");
172
172
  });
173
173
 
174
174
  expect(
@@ -179,16 +179,16 @@ describe("peer reconciliation", () => {
179
179
  ).toMatchInlineSnapshot(`
180
180
  [
181
181
  "client -> server | LOAD Group sessions: header/3",
182
- "client -> server | LOAD Map sessions: header/2",
183
182
  "server -> client | KNOWN Group sessions: empty",
184
- "client -> server | CONTENT Group header: true new: After: 0 New: 3",
183
+ "client -> server | LOAD Map sessions: header/2",
185
184
  "server -> client | KNOWN Map sessions: empty",
186
- "client -> server | CONTENT Map header: true new: After: 0 New: 2",
185
+ "client -> server | CONTENT Group header: true new: After: 0 New: 3",
187
186
  "server -> client | KNOWN Group sessions: header/3",
187
+ "client -> server | CONTENT Map header: true new: After: 0 New: 2",
188
188
  "server -> client | KNOWN Map sessions: header/2",
189
189
  "client -> server | LOAD Group sessions: header/3",
190
- "client -> server | LOAD Map sessions: header/2",
191
190
  "server -> client | KNOWN Group sessions: header/3",
191
+ "client -> server | LOAD Map sessions: header/2",
192
192
  "server -> client | KNOWN Map sessions: header/2",
193
193
  ]
194
194
  `);
@@ -210,7 +210,7 @@ describe("peer reconciliation", () => {
210
210
 
211
211
  SyncMessagesLog.clear();
212
212
  client.connectToSyncServer();
213
- const mapOnSyncServer = jazzCloud.node.coValuesStore.get(map.id);
213
+ const mapOnSyncServer = jazzCloud.node.getCoValue(map.id);
214
214
 
215
215
  await waitFor(() => {
216
216
  expect(mapOnSyncServer.isAvailable()).toBe(true);
@@ -232,8 +232,8 @@ describe("peer reconciliation", () => {
232
232
  ]
233
233
  `);
234
234
 
235
- expect(
236
- expectMap(mapOnSyncServer.core.getCurrentContent()).get("hello"),
237
- ).toEqual("updated");
235
+ expect(expectMap(mapOnSyncServer.getCurrentContent()).get("hello")).toEqual(
236
+ "updated",
237
+ );
238
238
  });
239
239
  });