cojson 0.13.5 → 0.13.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +10 -0
- package/LICENSE.txt +1 -1
- package/dist/PeerState.d.ts +6 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +43 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore.js +1 -0
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValueState.d.ts +1 -0
- package/dist/coValueState.d.ts.map +1 -1
- package/dist/coValueState.js +27 -2
- package/dist/coValueState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +45 -21
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/crypto.d.ts +2 -2
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +19 -3
- package/dist/permissions.js.map +1 -1
- package/dist/storage/FileSystem.d.ts +2 -2
- package/dist/storage/FileSystem.d.ts.map +1 -1
- package/dist/sync.d.ts +14 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +146 -146
- package/dist/sync.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +51 -46
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +51 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueState.test.js +31 -4
- package/dist/tests/coValueState.test.js.map +1 -1
- package/dist/tests/group.test.js +135 -2
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +13 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/sync.load.test.d.ts +2 -0
- package/dist/tests/sync.load.test.d.ts.map +1 -0
- package/dist/tests/sync.load.test.js +249 -0
- package/dist/tests/sync.load.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.d.ts +2 -0
- package/dist/tests/sync.mesh.test.d.ts.map +1 -0
- package/dist/tests/sync.mesh.test.js +157 -0
- package/dist/tests/sync.mesh.test.js.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.js +130 -0
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
- package/dist/tests/sync.storage.test.d.ts +2 -0
- package/dist/tests/sync.storage.test.d.ts.map +1 -0
- package/dist/tests/sync.storage.test.js +201 -0
- package/dist/tests/sync.storage.test.js.map +1 -0
- package/dist/tests/sync.test.js +139 -1048
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.d.ts +2 -0
- package/dist/tests/sync.upload.test.d.ts.map +1 -0
- package/dist/tests/sync.upload.test.js +156 -0
- package/dist/tests/sync.upload.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +76 -33
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +153 -47
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/PeerState.ts +59 -1
- package/src/coValueCore.ts +1 -0
- package/src/coValueState.ts +34 -3
- package/src/coValues/group.ts +83 -45
- package/src/permissions.ts +31 -3
- package/src/sync.ts +169 -185
- package/src/tests/SyncStateManager.test.ts +58 -70
- package/src/tests/coValueCore.test.ts +70 -1
- package/src/tests/coValueState.test.ts +59 -5
- package/src/tests/group.test.ts +250 -2
- package/src/tests/messagesTestUtils.ts +75 -0
- package/src/tests/sync.load.test.ts +327 -0
- package/src/tests/sync.mesh.test.ts +219 -0
- package/src/tests/sync.peerReconciliation.test.ts +201 -0
- package/src/tests/sync.storage.test.ts +259 -0
- package/src/tests/sync.test.ts +170 -1245
- package/src/tests/sync.upload.test.ts +202 -0
- package/src/tests/testUtils.ts +213 -61
package/src/tests/sync.test.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assert,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
test,
|
|
8
|
+
vi,
|
|
9
|
+
} from "vitest";
|
|
2
10
|
import { expectMap } from "../coValue.js";
|
|
3
11
|
import type { CoValueHeader, TryAddTransactionsError } from "../coValueCore.js";
|
|
4
12
|
import type { RawAccountID } from "../coValues/account.js";
|
|
@@ -12,473 +20,27 @@ import { connectedPeers, newQueuePair } from "../streamUtils.js";
|
|
|
12
20
|
import type { LoadMessage, SyncMessage } from "../sync.js";
|
|
13
21
|
import {
|
|
14
22
|
blockMessageTypeOnOutgoingPeer,
|
|
15
|
-
connectNodeToSyncServer,
|
|
16
23
|
connectTwoPeers,
|
|
17
|
-
createConnectedTestAgentNode,
|
|
18
|
-
createConnectedTestNode,
|
|
19
24
|
createTestMetricReader,
|
|
20
25
|
createTestNode,
|
|
21
26
|
loadCoValueOrFail,
|
|
22
27
|
randomAnonymousAccountAndSessionID,
|
|
23
|
-
|
|
28
|
+
setupTestAccount,
|
|
29
|
+
setupTestNode,
|
|
24
30
|
tearDownTestMetricReader,
|
|
25
31
|
waitFor,
|
|
26
32
|
} from "./testUtils.js";
|
|
27
33
|
|
|
28
34
|
const Crypto = await WasmCrypto.create();
|
|
29
35
|
|
|
30
|
-
let jazzCloud =
|
|
31
|
-
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
jazzCloud = setupSyncServer();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
37
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
38
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
39
|
-
|
|
40
|
-
const group = node.createGroup();
|
|
41
|
-
|
|
42
|
-
const map = group.createMap();
|
|
43
|
-
|
|
44
|
-
map.set("hello", "world", "trusting");
|
|
45
|
-
|
|
46
|
-
const [inRx, inTx] = newQueuePair();
|
|
47
|
-
const [outRx, outTx] = newQueuePair();
|
|
48
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
49
|
-
|
|
50
|
-
node.syncManager.addPeer({
|
|
51
|
-
id: "test",
|
|
52
|
-
incoming: inRx,
|
|
53
|
-
outgoing: outTx,
|
|
54
|
-
role: "peer",
|
|
55
|
-
crashOnClose: true,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
await inTx.push({
|
|
59
|
-
action: "load",
|
|
60
|
-
id: map.core.id,
|
|
61
|
-
header: false,
|
|
62
|
-
sessions: {},
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
66
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
67
|
-
|
|
68
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
69
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
70
|
-
action: "known",
|
|
71
|
-
...map.core.knownState(),
|
|
72
|
-
} satisfies SyncMessage);
|
|
73
|
-
|
|
74
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
75
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
76
|
-
|
|
77
|
-
const newContentMsg = (await outRxQ.next()).value;
|
|
78
|
-
|
|
79
|
-
const expectedHeader = {
|
|
80
|
-
type: "comap",
|
|
81
|
-
ruleset: { type: "ownedByGroup", group: group.id },
|
|
82
|
-
meta: null,
|
|
83
|
-
createdAt: map.core.header.createdAt,
|
|
84
|
-
uniqueness: map.core.header.uniqueness,
|
|
85
|
-
} satisfies CoValueHeader;
|
|
86
|
-
|
|
87
|
-
expect(newContentMsg).toEqual({
|
|
88
|
-
action: "content",
|
|
89
|
-
id: map.core.id,
|
|
90
|
-
header: expectedHeader,
|
|
91
|
-
new: {
|
|
92
|
-
[node.currentSessionID]: {
|
|
93
|
-
after: 0,
|
|
94
|
-
newTransactions: [
|
|
95
|
-
{
|
|
96
|
-
privacy: "trusting" as const,
|
|
97
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
98
|
-
.transactions[0]!.madeAt,
|
|
99
|
-
changes: stableStringify([
|
|
100
|
-
{
|
|
101
|
-
op: "set",
|
|
102
|
-
key: "hello",
|
|
103
|
-
value: "world",
|
|
104
|
-
} satisfies MapOpPayload<string, string>,
|
|
105
|
-
]),
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
109
|
-
.lastSignature!,
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
113
|
-
} satisfies SyncMessage);
|
|
36
|
+
let jazzCloud = setupTestNode({
|
|
37
|
+
isSyncServer: true,
|
|
114
38
|
});
|
|
115
39
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const group = node.createGroup();
|
|
121
|
-
|
|
122
|
-
const map = group.createMap();
|
|
123
|
-
|
|
124
|
-
map.set("hello", "world", "trusting");
|
|
125
|
-
map.set("goodbye", "world", "trusting");
|
|
126
|
-
|
|
127
|
-
const [inRx, inTx] = newQueuePair();
|
|
128
|
-
const [outRx, outTx] = newQueuePair();
|
|
129
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
130
|
-
|
|
131
|
-
node.syncManager.addPeer({
|
|
132
|
-
id: "test",
|
|
133
|
-
incoming: inRx,
|
|
134
|
-
outgoing: outTx,
|
|
135
|
-
role: "peer",
|
|
136
|
-
crashOnClose: true,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
await inTx.push({
|
|
140
|
-
action: "load",
|
|
141
|
-
id: map.core.id,
|
|
142
|
-
header: true,
|
|
143
|
-
sessions: {
|
|
144
|
-
[node.currentSessionID]: 1,
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
149
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
150
|
-
|
|
151
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
152
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
153
|
-
action: "known",
|
|
154
|
-
...map.core.knownState(),
|
|
155
|
-
} satisfies SyncMessage);
|
|
156
|
-
|
|
157
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
158
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
159
|
-
|
|
160
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
161
|
-
|
|
162
|
-
expect(mapNewContentMsg).toEqual({
|
|
163
|
-
action: "content",
|
|
164
|
-
id: map.core.id,
|
|
165
|
-
header: undefined,
|
|
166
|
-
new: {
|
|
167
|
-
[node.currentSessionID]: {
|
|
168
|
-
after: 1,
|
|
169
|
-
newTransactions: [
|
|
170
|
-
{
|
|
171
|
-
privacy: "trusting" as const,
|
|
172
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
173
|
-
.transactions[1]!.madeAt,
|
|
174
|
-
changes: stableStringify([
|
|
175
|
-
{
|
|
176
|
-
op: "set",
|
|
177
|
-
key: "goodbye",
|
|
178
|
-
value: "world",
|
|
179
|
-
} satisfies MapOpPayload<string, string>,
|
|
180
|
-
]),
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
184
|
-
.lastSignature!,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
188
|
-
} satisfies SyncMessage);
|
|
189
|
-
});
|
|
190
|
-
test.todo(
|
|
191
|
-
"TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues",
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
test("After subscribing, node sends own known state and new txs to peer", async () => {
|
|
195
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
196
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
197
|
-
|
|
198
|
-
const group = node.createGroup();
|
|
199
|
-
|
|
200
|
-
const map = group.createMap();
|
|
201
|
-
|
|
202
|
-
const [inRx, inTx] = newQueuePair();
|
|
203
|
-
const [outRx, outTx] = newQueuePair();
|
|
204
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
205
|
-
|
|
206
|
-
node.syncManager.addPeer({
|
|
207
|
-
id: "test",
|
|
208
|
-
incoming: inRx,
|
|
209
|
-
outgoing: outTx,
|
|
210
|
-
role: "peer",
|
|
211
|
-
crashOnClose: true,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
await inTx.push({
|
|
215
|
-
action: "load",
|
|
216
|
-
id: map.core.id,
|
|
217
|
-
header: false,
|
|
218
|
-
sessions: {
|
|
219
|
-
[node.currentSessionID]: 0,
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
224
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
225
|
-
|
|
226
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
227
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
228
|
-
action: "known",
|
|
229
|
-
...map.core.knownState(),
|
|
230
|
-
} satisfies SyncMessage);
|
|
231
|
-
|
|
232
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
233
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
234
|
-
|
|
235
|
-
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
236
|
-
|
|
237
|
-
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
238
|
-
action: "content",
|
|
239
|
-
id: map.core.id,
|
|
240
|
-
header: map.core.header,
|
|
241
|
-
new: {},
|
|
242
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
243
|
-
} satisfies SyncMessage);
|
|
244
|
-
|
|
245
|
-
map.set("hello", "world", "trusting");
|
|
246
|
-
|
|
247
|
-
const mapEditMsg1 = (await outRxQ.next()).value;
|
|
248
|
-
|
|
249
|
-
expect(mapEditMsg1).toEqual({
|
|
250
|
-
action: "content",
|
|
251
|
-
id: map.core.id,
|
|
252
|
-
new: {
|
|
253
|
-
[node.currentSessionID]: {
|
|
254
|
-
after: 0,
|
|
255
|
-
newTransactions: [
|
|
256
|
-
{
|
|
257
|
-
privacy: "trusting" as const,
|
|
258
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
259
|
-
.transactions[0]!.madeAt,
|
|
260
|
-
changes: stableStringify([
|
|
261
|
-
{
|
|
262
|
-
op: "set",
|
|
263
|
-
key: "hello",
|
|
264
|
-
value: "world",
|
|
265
|
-
} satisfies MapOpPayload<string, string>,
|
|
266
|
-
]),
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
270
|
-
.lastSignature!,
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
274
|
-
} satisfies SyncMessage);
|
|
275
|
-
|
|
276
|
-
map.set("goodbye", "world", "trusting");
|
|
277
|
-
|
|
278
|
-
const mapEditMsg2 = (await outRxQ.next()).value;
|
|
279
|
-
|
|
280
|
-
expect(mapEditMsg2).toEqual({
|
|
281
|
-
action: "content",
|
|
282
|
-
id: map.core.id,
|
|
283
|
-
new: {
|
|
284
|
-
[node.currentSessionID]: {
|
|
285
|
-
after: 1,
|
|
286
|
-
newTransactions: [
|
|
287
|
-
{
|
|
288
|
-
privacy: "trusting" as const,
|
|
289
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
290
|
-
.transactions[1]!.madeAt,
|
|
291
|
-
changes: stableStringify([
|
|
292
|
-
{
|
|
293
|
-
op: "set",
|
|
294
|
-
key: "goodbye",
|
|
295
|
-
value: "world",
|
|
296
|
-
} satisfies MapOpPayload<string, string>,
|
|
297
|
-
]),
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
301
|
-
.lastSignature!,
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
305
|
-
} satisfies SyncMessage);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("Client replies with known new content to tellKnownState from server", async () => {
|
|
309
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
310
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
311
|
-
|
|
312
|
-
const group = node.createGroup();
|
|
313
|
-
|
|
314
|
-
const map = group.createMap();
|
|
315
|
-
|
|
316
|
-
map.set("hello", "world", "trusting");
|
|
317
|
-
|
|
318
|
-
const [inRx, inTx] = newQueuePair();
|
|
319
|
-
const [outRx, outTx] = newQueuePair();
|
|
320
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
321
|
-
|
|
322
|
-
node.syncManager.addPeer({
|
|
323
|
-
id: "test",
|
|
324
|
-
incoming: inRx,
|
|
325
|
-
outgoing: outTx,
|
|
326
|
-
role: "peer",
|
|
327
|
-
crashOnClose: true,
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
331
|
-
|
|
332
|
-
await inTx.push({
|
|
333
|
-
action: "known",
|
|
334
|
-
id: map.core.id,
|
|
335
|
-
header: false,
|
|
336
|
-
sessions: {
|
|
337
|
-
[node.currentSessionID]: 0,
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
342
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
343
|
-
|
|
344
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
345
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
346
|
-
action: "known",
|
|
347
|
-
...map.core.knownState(),
|
|
348
|
-
} satisfies SyncMessage);
|
|
349
|
-
|
|
350
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
351
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
352
|
-
|
|
353
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
354
|
-
|
|
355
|
-
expect(mapNewContentMsg).toEqual({
|
|
356
|
-
action: "content",
|
|
357
|
-
id: map.core.id,
|
|
358
|
-
header: map.core.header,
|
|
359
|
-
new: {
|
|
360
|
-
[node.currentSessionID]: {
|
|
361
|
-
after: 0,
|
|
362
|
-
newTransactions: [
|
|
363
|
-
{
|
|
364
|
-
privacy: "trusting" as const,
|
|
365
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
366
|
-
.transactions[0]!.madeAt,
|
|
367
|
-
changes: stableStringify([
|
|
368
|
-
{
|
|
369
|
-
op: "set",
|
|
370
|
-
key: "hello",
|
|
371
|
-
value: "world",
|
|
372
|
-
} satisfies MapOpPayload<string, string>,
|
|
373
|
-
]),
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
377
|
-
.lastSignature!,
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
381
|
-
} satisfies SyncMessage);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
|
|
385
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
386
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
387
|
-
|
|
388
|
-
const group = node.createGroup();
|
|
389
|
-
|
|
390
|
-
const map = group.createMap();
|
|
391
|
-
|
|
392
|
-
const [inRx, inTx] = newQueuePair();
|
|
393
|
-
const [outRx, outTx] = newQueuePair();
|
|
394
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
395
|
-
|
|
396
|
-
node.syncManager.addPeer({
|
|
397
|
-
id: "test",
|
|
398
|
-
incoming: inRx,
|
|
399
|
-
outgoing: outTx,
|
|
400
|
-
role: "peer",
|
|
401
|
-
crashOnClose: true,
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
await inTx.push({
|
|
405
|
-
action: "load",
|
|
406
|
-
id: map.core.id,
|
|
407
|
-
header: false,
|
|
408
|
-
sessions: {
|
|
409
|
-
[node.currentSessionID]: 0,
|
|
410
|
-
},
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
jazzCloud = setupTestNode({
|
|
42
|
+
isSyncServer: true,
|
|
411
43
|
});
|
|
412
|
-
|
|
413
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
414
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
415
|
-
|
|
416
|
-
const mapTellKnownStateMsg = (await outRxQ.next()).value;
|
|
417
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
418
|
-
action: "known",
|
|
419
|
-
...map.core.knownState(),
|
|
420
|
-
} satisfies SyncMessage);
|
|
421
|
-
|
|
422
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
423
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
424
|
-
|
|
425
|
-
const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
|
|
426
|
-
|
|
427
|
-
expect(mapNewContentHeaderOnlyMsg).toEqual({
|
|
428
|
-
action: "content",
|
|
429
|
-
id: map.core.id,
|
|
430
|
-
header: map.core.header,
|
|
431
|
-
new: {},
|
|
432
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
433
|
-
} satisfies SyncMessage);
|
|
434
|
-
|
|
435
|
-
map.set("hello", "world", "trusting");
|
|
436
|
-
|
|
437
|
-
map.set("goodbye", "world", "trusting");
|
|
438
|
-
|
|
439
|
-
const _mapEditMsgs = (await outRxQ.next()).value;
|
|
440
|
-
|
|
441
|
-
console.log("Sending correction");
|
|
442
|
-
|
|
443
|
-
await inTx.push({
|
|
444
|
-
action: "known",
|
|
445
|
-
isCorrection: true,
|
|
446
|
-
id: map.core.id,
|
|
447
|
-
header: true,
|
|
448
|
-
sessions: {
|
|
449
|
-
[node.currentSessionID]: 1,
|
|
450
|
-
},
|
|
451
|
-
} satisfies SyncMessage);
|
|
452
|
-
|
|
453
|
-
const newContentAfterWrongAssumedState = (await outRxQ.next()).value;
|
|
454
|
-
|
|
455
|
-
expect(newContentAfterWrongAssumedState).toEqual({
|
|
456
|
-
action: "content",
|
|
457
|
-
id: map.core.id,
|
|
458
|
-
header: undefined,
|
|
459
|
-
new: {
|
|
460
|
-
[node.currentSessionID]: {
|
|
461
|
-
after: 1,
|
|
462
|
-
newTransactions: [
|
|
463
|
-
{
|
|
464
|
-
privacy: "trusting" as const,
|
|
465
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
466
|
-
.transactions[1]!.madeAt,
|
|
467
|
-
changes: stableStringify([
|
|
468
|
-
{
|
|
469
|
-
op: "set",
|
|
470
|
-
key: "goodbye",
|
|
471
|
-
value: "world",
|
|
472
|
-
} satisfies MapOpPayload<string, string>,
|
|
473
|
-
]),
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
477
|
-
.lastSignature!,
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
481
|
-
} satisfies SyncMessage);
|
|
482
44
|
});
|
|
483
45
|
|
|
484
46
|
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
|
|
@@ -515,370 +77,19 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m
|
|
|
515
77
|
expect(result).toEqual("neverHappened");
|
|
516
78
|
});
|
|
517
79
|
|
|
518
|
-
test.todo(
|
|
519
|
-
"If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe",
|
|
520
|
-
async () => {
|
|
521
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
522
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
523
|
-
|
|
524
|
-
const group = node.createGroup();
|
|
525
|
-
|
|
526
|
-
const map = group.createMap();
|
|
527
|
-
|
|
528
|
-
const [inRx, _inTx] = newQueuePair();
|
|
529
|
-
const [outRx, outTx] = newQueuePair();
|
|
530
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
531
|
-
|
|
532
|
-
node.syncManager.addPeer({
|
|
533
|
-
id: "test",
|
|
534
|
-
incoming: inRx,
|
|
535
|
-
outgoing: outTx,
|
|
536
|
-
role: "server",
|
|
537
|
-
crashOnClose: true,
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// expect((await outRxQ.next()).value).toMatchObject({
|
|
541
|
-
// action: "load",
|
|
542
|
-
// id: adminID,
|
|
543
|
-
// });
|
|
544
|
-
expect((await outRxQ.next()).value).toMatchObject({
|
|
545
|
-
action: "load",
|
|
546
|
-
id: group.core.id,
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
550
|
-
|
|
551
|
-
expect(mapSubscribeMsg).toEqual({
|
|
552
|
-
action: "load",
|
|
553
|
-
id: map.core.id,
|
|
554
|
-
header: true,
|
|
555
|
-
sessions: {},
|
|
556
|
-
} satisfies SyncMessage);
|
|
557
|
-
|
|
558
|
-
map.set("hello", "world", "trusting");
|
|
559
|
-
|
|
560
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
|
|
561
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
562
|
-
|
|
563
|
-
const mapNewContentMsg = (await outRxQ.next()).value;
|
|
564
|
-
|
|
565
|
-
expect(mapNewContentMsg).toEqual({
|
|
566
|
-
action: "content",
|
|
567
|
-
id: map.core.id,
|
|
568
|
-
header: map.core.header,
|
|
569
|
-
new: {
|
|
570
|
-
[node.currentSessionID]: {
|
|
571
|
-
after: 0,
|
|
572
|
-
newTransactions: [
|
|
573
|
-
{
|
|
574
|
-
privacy: "trusting" as const,
|
|
575
|
-
madeAt: map.core.sessionLogs.get(node.currentSessionID)!
|
|
576
|
-
.transactions[0]!.madeAt,
|
|
577
|
-
changes: stableStringify([
|
|
578
|
-
{
|
|
579
|
-
op: "set",
|
|
580
|
-
key: "hello",
|
|
581
|
-
value: "world",
|
|
582
|
-
} satisfies MapOpPayload<string, string>,
|
|
583
|
-
]),
|
|
584
|
-
},
|
|
585
|
-
],
|
|
586
|
-
lastSignature: map.core.sessionLogs.get(node.currentSessionID)!
|
|
587
|
-
.lastSignature!,
|
|
588
|
-
},
|
|
589
|
-
},
|
|
590
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
591
|
-
} satisfies SyncMessage);
|
|
592
|
-
},
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
test.skip("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
|
|
596
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
597
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
598
|
-
|
|
599
|
-
const group = node.createGroup();
|
|
600
|
-
|
|
601
|
-
const [inRx, _inTx] = newQueuePair();
|
|
602
|
-
const [outRx, outTx] = newQueuePair();
|
|
603
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
604
|
-
|
|
605
|
-
node.syncManager.addPeer({
|
|
606
|
-
id: "test",
|
|
607
|
-
incoming: inRx,
|
|
608
|
-
outgoing: outTx,
|
|
609
|
-
role: "server",
|
|
610
|
-
crashOnClose: true,
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
// expect((await outRxQ.next()).value).toMatchObject({
|
|
614
|
-
// action: "load",
|
|
615
|
-
// id: admin.id,
|
|
616
|
-
// });
|
|
617
|
-
expect((await outRxQ.next()).value).toMatchObject({
|
|
618
|
-
action: "load",
|
|
619
|
-
id: group.core.id,
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
const map = group.createMap();
|
|
623
|
-
|
|
624
|
-
const mapSubscribeMsg = (await outRxQ.next()).value;
|
|
625
|
-
|
|
626
|
-
expect(mapSubscribeMsg).toEqual({
|
|
627
|
-
action: "load",
|
|
628
|
-
...map.core.knownState(),
|
|
629
|
-
} satisfies SyncMessage);
|
|
630
|
-
|
|
631
|
-
// expect((await outRxQ.next()).value).toMatchObject(admContEx(adminID));
|
|
632
|
-
expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
|
|
633
|
-
|
|
634
|
-
const mapContentMsg = (await outRxQ.next()).value;
|
|
635
|
-
|
|
636
|
-
expect(mapContentMsg).toEqual({
|
|
637
|
-
action: "content",
|
|
638
|
-
id: map.core.id,
|
|
639
|
-
header: map.core.header,
|
|
640
|
-
new: {},
|
|
641
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
642
|
-
} satisfies SyncMessage);
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
test.todo(
|
|
646
|
-
"TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it",
|
|
647
|
-
);
|
|
648
|
-
|
|
649
|
-
test("When we connect a new server peer, we try to sync all existing coValues to it", async () => {
|
|
650
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
651
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
652
|
-
|
|
653
|
-
const group = node.createGroup();
|
|
654
|
-
|
|
655
|
-
const map = group.createMap();
|
|
656
|
-
|
|
657
|
-
const [inRx, _inTx] = newQueuePair();
|
|
658
|
-
const [outRx, outTx] = newQueuePair();
|
|
659
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
660
|
-
|
|
661
|
-
node.syncManager.addPeer({
|
|
662
|
-
id: "test",
|
|
663
|
-
incoming: inRx,
|
|
664
|
-
outgoing: outTx,
|
|
665
|
-
role: "server",
|
|
666
|
-
crashOnClose: true,
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
// const _adminSubscribeMessage = await outRxQ.next();
|
|
670
|
-
const groupSubscribeMessage = (await outRxQ.next()).value;
|
|
671
|
-
|
|
672
|
-
expect(groupSubscribeMessage).toEqual({
|
|
673
|
-
action: "load",
|
|
674
|
-
...group.core.knownState(),
|
|
675
|
-
} satisfies SyncMessage);
|
|
676
|
-
|
|
677
|
-
const secondMessage = (await outRxQ.next()).value;
|
|
678
|
-
|
|
679
|
-
expect(secondMessage).toEqual({
|
|
680
|
-
action: "load",
|
|
681
|
-
...map.core.knownState(),
|
|
682
|
-
} satisfies SyncMessage);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", async () => {
|
|
686
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
687
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
688
|
-
|
|
689
|
-
const group = node.createGroup();
|
|
690
|
-
|
|
691
|
-
const map = group.createMap();
|
|
692
|
-
|
|
693
|
-
const [inRx, inTx] = newQueuePair();
|
|
694
|
-
const [outRx, outTx] = newQueuePair();
|
|
695
|
-
const outRxQ = outRx[Symbol.asyncIterator]();
|
|
696
|
-
|
|
697
|
-
node.syncManager.addPeer({
|
|
698
|
-
id: "test",
|
|
699
|
-
incoming: inRx,
|
|
700
|
-
outgoing: outTx,
|
|
701
|
-
role: "peer",
|
|
702
|
-
crashOnClose: true,
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
await inTx.push({
|
|
706
|
-
action: "load",
|
|
707
|
-
id: map.core.id,
|
|
708
|
-
header: true,
|
|
709
|
-
sessions: {
|
|
710
|
-
[node.currentSessionID]: 1,
|
|
711
|
-
},
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
|
|
715
|
-
expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
|
|
716
|
-
const mapTellKnownState = (await outRxQ.next()).value;
|
|
717
|
-
|
|
718
|
-
expect(mapTellKnownState).toEqual({
|
|
719
|
-
action: "known",
|
|
720
|
-
...map.core.knownState(),
|
|
721
|
-
} satisfies SyncMessage);
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => {
|
|
725
|
-
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
726
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
727
|
-
|
|
728
|
-
const node1 = new LocalNode(admin, session, Crypto);
|
|
729
|
-
|
|
730
|
-
const group = node1.createGroup();
|
|
731
|
-
|
|
732
|
-
const [inRx1, inTx1] = newQueuePair();
|
|
733
|
-
const [outRx1, outTx1] = newQueuePair();
|
|
734
|
-
const outRxQ1 = outRx1[Symbol.asyncIterator]();
|
|
735
|
-
|
|
736
|
-
node1.syncManager.addPeer({
|
|
737
|
-
id: "test2",
|
|
738
|
-
incoming: inRx1,
|
|
739
|
-
outgoing: outTx1,
|
|
740
|
-
role: "server",
|
|
741
|
-
crashOnClose: true,
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
const node2 = new LocalNode(
|
|
745
|
-
admin,
|
|
746
|
-
Crypto.newRandomSessionID(admin.id),
|
|
747
|
-
Crypto,
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
const [inRx2, inTx2] = newQueuePair();
|
|
751
|
-
const [outRx2, outTx2] = newQueuePair();
|
|
752
|
-
const outRxQ2 = outRx2[Symbol.asyncIterator]();
|
|
753
|
-
|
|
754
|
-
node2.syncManager.addPeer({
|
|
755
|
-
id: "test1",
|
|
756
|
-
incoming: inRx2,
|
|
757
|
-
outgoing: outTx2,
|
|
758
|
-
role: "client",
|
|
759
|
-
crashOnClose: true,
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
const adminSubscribeMessage = (await outRxQ1.next()).value;
|
|
763
|
-
expect(adminSubscribeMessage).toMatchObject({
|
|
764
|
-
action: "load",
|
|
765
|
-
id: admin.id,
|
|
766
|
-
});
|
|
767
|
-
const groupSubscribeMsg = (await outRxQ1.next()).value;
|
|
768
|
-
expect(groupSubscribeMsg).toMatchObject({
|
|
769
|
-
action: "load",
|
|
770
|
-
id: group.core.id,
|
|
771
|
-
});
|
|
772
|
-
|
|
773
|
-
await inTx2.push(adminSubscribeMessage);
|
|
774
|
-
await inTx2.push(groupSubscribeMsg);
|
|
775
|
-
|
|
776
|
-
// const adminTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
777
|
-
// expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
|
|
778
|
-
|
|
779
|
-
const groupTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
780
|
-
expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
|
|
781
|
-
|
|
782
|
-
expect(
|
|
783
|
-
node2.syncManager.peers["test1"]!.optimisticKnownStates.has(group.core.id),
|
|
784
|
-
).toBeDefined();
|
|
785
|
-
|
|
786
|
-
// await inTx1.push(adminTellKnownStateMsg);
|
|
787
|
-
await inTx1.push(groupTellKnownStateMsg);
|
|
788
|
-
|
|
789
|
-
// const adminContentMsg = (await outRxQ1.next()).value;
|
|
790
|
-
// expect(adminContentMsg).toMatchObject(admContEx(admin.id));
|
|
791
|
-
|
|
792
|
-
const groupContentMsg = (await outRxQ1.next()).value;
|
|
793
|
-
expect(groupContentMsg).toMatchObject(groupContentEx(group));
|
|
794
|
-
|
|
795
|
-
// await inTx2.push(adminContentMsg);
|
|
796
|
-
await inTx2.push(groupContentMsg);
|
|
797
|
-
|
|
798
|
-
const map = group.createMap();
|
|
799
|
-
|
|
800
|
-
const mapSubscriptionMsg = (await outRxQ1.next()).value;
|
|
801
|
-
expect(mapSubscriptionMsg).toMatchObject({
|
|
802
|
-
action: "load",
|
|
803
|
-
id: map.core.id,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
const mapNewContentMsg = (await outRxQ1.next()).value;
|
|
807
|
-
expect(mapNewContentMsg).toEqual({
|
|
808
|
-
action: "content",
|
|
809
|
-
id: map.core.id,
|
|
810
|
-
header: map.core.header,
|
|
811
|
-
new: {},
|
|
812
|
-
priority: getPriorityFromHeader(map.core.header),
|
|
813
|
-
} satisfies SyncMessage);
|
|
814
|
-
|
|
815
|
-
await inTx2.push(mapSubscriptionMsg);
|
|
816
|
-
|
|
817
|
-
const mapTellKnownStateMsg = (await outRxQ2.next()).value;
|
|
818
|
-
expect(mapTellKnownStateMsg).toEqual({
|
|
819
|
-
action: "known",
|
|
820
|
-
id: map.core.id,
|
|
821
|
-
header: false,
|
|
822
|
-
sessions: {},
|
|
823
|
-
} satisfies SyncMessage);
|
|
824
|
-
|
|
825
|
-
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("loading");
|
|
826
|
-
|
|
827
|
-
await inTx2.push(mapNewContentMsg);
|
|
828
|
-
|
|
829
|
-
map.set("hello", "world", "trusting");
|
|
830
|
-
|
|
831
|
-
const mapEditMsg = (await outRxQ1.next()).value;
|
|
832
|
-
|
|
833
|
-
await inTx2.push(mapEditMsg);
|
|
834
|
-
|
|
835
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
836
|
-
|
|
837
|
-
expect(
|
|
838
|
-
expectMap(node2.expectCoValueLoaded(map.core.id).getCurrentContent()).get(
|
|
839
|
-
"hello",
|
|
840
|
-
),
|
|
841
|
-
).toEqual("world");
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
test.skip("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => {
|
|
845
|
-
/*
|
|
846
|
-
// TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work
|
|
847
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
848
|
-
|
|
849
|
-
const node1 = new LocalNode(admin, session, Crypto);
|
|
850
|
-
|
|
851
|
-
const group = node1.createGroup();
|
|
852
|
-
|
|
853
|
-
const map = group.createMap();
|
|
854
|
-
map.set("hello", "world", "trusting");
|
|
855
|
-
|
|
856
|
-
const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
|
|
857
|
-
|
|
858
|
-
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2");
|
|
859
|
-
|
|
860
|
-
node1.syncManager.addPeer(node2asPeer);
|
|
861
|
-
node2.syncManager.addPeer(node1asPeer);
|
|
862
|
-
|
|
863
|
-
await node2.loadCoValueCore(map.core.id);
|
|
864
|
-
|
|
865
|
-
expect(
|
|
866
|
-
expectMap(
|
|
867
|
-
node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
|
|
868
|
-
).get("hello"),
|
|
869
|
-
).toEqual("world");
|
|
870
|
-
*/
|
|
871
|
-
});
|
|
872
|
-
|
|
873
80
|
test("Can sync a coValue through a server to another client", async () => {
|
|
874
|
-
const { node: client1 } = await
|
|
81
|
+
const { node: client1 } = await setupTestAccount({
|
|
82
|
+
connected: true,
|
|
83
|
+
});
|
|
875
84
|
|
|
876
85
|
const group = client1.createGroup();
|
|
877
86
|
|
|
878
87
|
const map = group.createMap();
|
|
879
88
|
map.set("hello", "world", "trusting");
|
|
880
89
|
|
|
881
|
-
const { node: client2 } = await
|
|
90
|
+
const { node: client2 } = await setupTestAccount({
|
|
91
|
+
connected: true,
|
|
92
|
+
});
|
|
882
93
|
|
|
883
94
|
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
884
95
|
|
|
@@ -886,7 +97,9 @@ test("Can sync a coValue through a server to another client", async () => {
|
|
|
886
97
|
});
|
|
887
98
|
|
|
888
99
|
test("Can sync a coValue with private transactions through a server to another client", async () => {
|
|
889
|
-
const { node: client1 } = await
|
|
100
|
+
const { node: client1 } = await setupTestAccount({
|
|
101
|
+
connected: true,
|
|
102
|
+
});
|
|
890
103
|
|
|
891
104
|
const group = client1.createGroup();
|
|
892
105
|
|
|
@@ -894,115 +107,57 @@ test("Can sync a coValue with private transactions through a server to another c
|
|
|
894
107
|
map.set("hello", "world", "private");
|
|
895
108
|
group.addMember("everyone", "reader");
|
|
896
109
|
|
|
897
|
-
const { node: client2 } = await
|
|
110
|
+
const { node: client2 } = await setupTestAccount({
|
|
111
|
+
connected: true,
|
|
112
|
+
});
|
|
898
113
|
|
|
899
114
|
const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
|
|
900
115
|
|
|
901
116
|
expect(mapOnClient2.get("hello")).toEqual("world");
|
|
902
117
|
});
|
|
903
118
|
|
|
904
|
-
test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
|
|
905
|
-
/*
|
|
906
|
-
const [admin, session] = randomAnonymousAccountAndSessionID();
|
|
907
|
-
const node = new LocalNode(admin, session, Crypto);
|
|
908
|
-
|
|
909
|
-
const group = node.createGroup();
|
|
910
|
-
|
|
911
|
-
const [inRx, inTx] = await Effect.runPromise(newStreamPair());
|
|
912
|
-
const [outRx, outTx] = await Effect.runPromise(newStreamPair());
|
|
913
|
-
|
|
914
|
-
node.syncManager.addPeer({
|
|
915
|
-
id: "test",
|
|
916
|
-
incoming: inRx,
|
|
917
|
-
outgoing: outTx,
|
|
918
|
-
role: "server",
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject({
|
|
922
|
-
// action: "load",
|
|
923
|
-
// id: admin.id,
|
|
924
|
-
// });
|
|
925
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject({
|
|
926
|
-
action: "load",
|
|
927
|
-
id: group.core.id,
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
const map = group.createMap();
|
|
931
|
-
|
|
932
|
-
const mapSubscribeMsg = await reader.read();
|
|
933
|
-
|
|
934
|
-
expect(mapSubscribeMsg.value).toEqual({
|
|
935
|
-
action: "load",
|
|
936
|
-
...map.core.knownState(),
|
|
937
|
-
} satisfies SyncMessage);
|
|
938
|
-
|
|
939
|
-
// expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
|
|
940
|
-
expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
|
|
941
|
-
|
|
942
|
-
const mapContentMsg = await reader.read();
|
|
943
|
-
|
|
944
|
-
expect(mapContentMsg.value).toEqual({
|
|
945
|
-
action: "content",
|
|
946
|
-
id: map.core.id,
|
|
947
|
-
header: map.core.header,
|
|
948
|
-
new: {},
|
|
949
|
-
} satisfies SyncMessage);
|
|
950
|
-
|
|
951
|
-
await inTx.abort();
|
|
952
|
-
|
|
953
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
954
|
-
|
|
955
|
-
expect(node.syncManager.peers["test"]).toBeUndefined();
|
|
956
|
-
*/
|
|
957
|
-
});
|
|
958
|
-
|
|
959
119
|
test("should keep the peer state when the peer closes", async () => {
|
|
960
|
-
const client =
|
|
120
|
+
const client = setupTestNode();
|
|
961
121
|
|
|
962
|
-
const {
|
|
963
|
-
connectNodeToSyncServer(client);
|
|
122
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
964
123
|
|
|
965
|
-
const group = jazzCloud.createGroup();
|
|
124
|
+
const group = jazzCloud.node.createGroup();
|
|
966
125
|
const map = group.createMap();
|
|
967
126
|
map.set("hello", "world", "trusting");
|
|
968
127
|
|
|
969
|
-
await client.loadCoValueCore(map.core.id);
|
|
128
|
+
await client.node.loadCoValueCore(map.core.id);
|
|
970
129
|
|
|
971
|
-
const syncManager = client.syncManager;
|
|
972
|
-
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
130
|
+
const syncManager = client.node.syncManager;
|
|
973
131
|
|
|
974
132
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
975
|
-
await
|
|
133
|
+
await peer.outgoing.push("Disconnected");
|
|
976
134
|
|
|
977
135
|
await waitFor(() => peerState?.closed);
|
|
978
136
|
|
|
979
|
-
expect(syncManager.peers[
|
|
137
|
+
expect(syncManager.peers[peer.id]).not.toBeUndefined();
|
|
980
138
|
});
|
|
981
139
|
|
|
982
140
|
test("should delete the peer state when the peer closes if deletePeerStateOnClose is true", async () => {
|
|
983
|
-
const client =
|
|
141
|
+
const client = setupTestNode();
|
|
984
142
|
|
|
985
|
-
const {
|
|
986
|
-
connectNodeToSyncServer(client);
|
|
143
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
987
144
|
|
|
988
|
-
|
|
145
|
+
peer.deletePeerStateOnClose = true;
|
|
989
146
|
|
|
990
|
-
const group = jazzCloud.createGroup();
|
|
147
|
+
const group = jazzCloud.node.createGroup();
|
|
991
148
|
const map = group.createMap();
|
|
992
149
|
map.set("hello", "world", "trusting");
|
|
993
150
|
|
|
994
|
-
await client.loadCoValueCore(map.core.id);
|
|
151
|
+
await client.node.loadCoValueCore(map.core.id);
|
|
995
152
|
|
|
996
|
-
const syncManager = client.syncManager;
|
|
997
|
-
|
|
998
|
-
const peerState = syncManager.peers[nodeToServerPeer.id];
|
|
153
|
+
const syncManager = client.node.syncManager;
|
|
999
154
|
|
|
1000
155
|
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
1001
|
-
await
|
|
156
|
+
await peer.outgoing.push("Disconnected");
|
|
1002
157
|
|
|
1003
158
|
await waitFor(() => peerState?.closed);
|
|
1004
159
|
|
|
1005
|
-
expect(syncManager.peers[
|
|
160
|
+
expect(syncManager.peers[peer.id]).toBeUndefined();
|
|
1006
161
|
});
|
|
1007
162
|
|
|
1008
163
|
describe("sync - extra tests", () => {
|
|
@@ -1203,76 +358,6 @@ describe("sync - extra tests", () => {
|
|
|
1203
358
|
expect(finalStateNode2.toJSON()).toEqual(expectedState);
|
|
1204
359
|
expect(finalStateNode3.toJSON()).toEqual(expectedState);
|
|
1205
360
|
});
|
|
1206
|
-
test.skip("Large coValues are synced efficiently in chunks", async () => {
|
|
1207
|
-
// Create two nodes
|
|
1208
|
-
const [admin1, session1] = randomAnonymousAccountAndSessionID();
|
|
1209
|
-
const node1 = new LocalNode(admin1, session1, Crypto);
|
|
1210
|
-
|
|
1211
|
-
const [admin2, session2] = randomAnonymousAccountAndSessionID();
|
|
1212
|
-
const node2 = new LocalNode(admin2, session2, Crypto);
|
|
1213
|
-
|
|
1214
|
-
// Create a group and a large map on node1
|
|
1215
|
-
const group = node1.createGroup();
|
|
1216
|
-
group.addMember("everyone", "writer");
|
|
1217
|
-
const largeMap = group.createMap();
|
|
1218
|
-
|
|
1219
|
-
// Generate a large amount of data (about 10MB)
|
|
1220
|
-
const dataSize = 1 * 1024 * 1024;
|
|
1221
|
-
const chunkSize = 1024; // 1KB chunks
|
|
1222
|
-
const chunks = dataSize / chunkSize;
|
|
1223
|
-
|
|
1224
|
-
for (let i = 0; i < chunks; i++) {
|
|
1225
|
-
const key = `key${i}`;
|
|
1226
|
-
const value = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
|
|
1227
|
-
largeMap.set(key, value, "trusting");
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// Connect the nodes
|
|
1231
|
-
const [node1AsPeer, node2AsPeer] = connectedPeers("node1", "node2", {
|
|
1232
|
-
peer1role: "server",
|
|
1233
|
-
peer2role: "client",
|
|
1234
|
-
});
|
|
1235
|
-
|
|
1236
|
-
node1.syncManager.addPeer(node2AsPeer);
|
|
1237
|
-
node2.syncManager.addPeer(node1AsPeer);
|
|
1238
|
-
|
|
1239
|
-
await new Promise((resolve) => setTimeout(resolve, 4000));
|
|
1240
|
-
|
|
1241
|
-
// Measure sync time
|
|
1242
|
-
const startSync = performance.now();
|
|
1243
|
-
|
|
1244
|
-
// Load the large map on node2
|
|
1245
|
-
const largeMapOnNode2 = await node2.loadCoValueCore(largeMap.core.id);
|
|
1246
|
-
if (largeMapOnNode2 === "unavailable") {
|
|
1247
|
-
throw new Error("Large map is unavailable on node2");
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
const endSync = performance.now();
|
|
1251
|
-
const syncTime = endSync - startSync;
|
|
1252
|
-
|
|
1253
|
-
// Verify that all data was synced correctly
|
|
1254
|
-
const syncedMap = new RawCoMap(largeMapOnNode2);
|
|
1255
|
-
expect(
|
|
1256
|
-
Object.keys(largeMapOnNode2.getCurrentContent().toJSON() || {}).length,
|
|
1257
|
-
).toBe(chunks);
|
|
1258
|
-
|
|
1259
|
-
for (let i = 0; i < chunks; i++) {
|
|
1260
|
-
const key = `key${i}`;
|
|
1261
|
-
const expectedValue = Buffer.alloc(chunkSize, `value${i}`).toString(
|
|
1262
|
-
"base64",
|
|
1263
|
-
);
|
|
1264
|
-
expect(syncedMap.get(key)).toBe(expectedValue);
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// Check that sync time is reasonable (this threshold may need adjustment)
|
|
1268
|
-
const reasonableSyncTime = 10; // 30 seconds
|
|
1269
|
-
expect(syncTime).toBeLessThan(reasonableSyncTime);
|
|
1270
|
-
|
|
1271
|
-
// Check memory usage (this threshold may need adjustment)
|
|
1272
|
-
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
|
|
1273
|
-
const reasonableMemoryUsage = 1; // 500 MB
|
|
1274
|
-
expect(memoryUsage).toBeLessThan(reasonableMemoryUsage);
|
|
1275
|
-
});
|
|
1276
361
|
|
|
1277
362
|
test("Node correctly handles and recovers from network partitions", async () => {
|
|
1278
363
|
// Create three nodes
|
|
@@ -1479,7 +564,9 @@ test("a value created on one node can be loaded on anotehr node even if not dire
|
|
|
1479
564
|
|
|
1480
565
|
describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
1481
566
|
test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
|
|
1482
|
-
const { node: client } = await
|
|
567
|
+
const { node: client } = await setupTestAccount({
|
|
568
|
+
connected: true,
|
|
569
|
+
});
|
|
1483
570
|
|
|
1484
571
|
// Create test data
|
|
1485
572
|
const group = client.createGroup();
|
|
@@ -1492,7 +579,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1492
579
|
await mapOnClient.core.waitForSync();
|
|
1493
580
|
|
|
1494
581
|
const peerStateClient = client.syncManager.getPeers()[0]!;
|
|
1495
|
-
const peerStateJazzCloud = jazzCloud.syncManager.getPeers()[0]!;
|
|
582
|
+
const peerStateJazzCloud = jazzCloud.node.syncManager.getPeers()[0]!;
|
|
1496
583
|
|
|
1497
584
|
// The optimisticKnownStates should be the same as the knownStates after the full sync is complete
|
|
1498
585
|
expect(
|
|
@@ -1506,16 +593,18 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1506
593
|
});
|
|
1507
594
|
|
|
1508
595
|
test("optimisticKnownStates is updated as new transactions are sent, while knownStates only when the updates are acknowledged", async () => {
|
|
1509
|
-
const
|
|
596
|
+
const client = await setupTestAccount();
|
|
597
|
+
|
|
598
|
+
const { peer, peerState } = client.connectToSyncServer();
|
|
1510
599
|
|
|
1511
600
|
// Create test data and sync the first change
|
|
1512
601
|
// We want that both the nodes know about the coValue so we can test
|
|
1513
602
|
// the content acknowledgement flow.
|
|
1514
|
-
const group = client.createGroup();
|
|
603
|
+
const group = client.node.createGroup();
|
|
1515
604
|
const map = group.createMap();
|
|
1516
605
|
map.set("key1", "value1", "trusting");
|
|
1517
606
|
|
|
1518
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
607
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1519
608
|
await map.core.waitForSync();
|
|
1520
609
|
|
|
1521
610
|
// Block the content messages
|
|
@@ -1523,16 +612,12 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1523
612
|
// optimisticKnownStates is updated when the content messages are sent,
|
|
1524
613
|
// while knownStates is only updated when we receive the "known" messages
|
|
1525
614
|
// that are acknowledging the receipt of the content messages
|
|
1526
|
-
const outgoing = blockMessageTypeOnOutgoingPeer(
|
|
1527
|
-
nodeToServerPeer,
|
|
1528
|
-
"content",
|
|
1529
|
-
);
|
|
615
|
+
const outgoing = blockMessageTypeOnOutgoingPeer(peer, "content");
|
|
1530
616
|
|
|
1531
617
|
map.set("key2", "value2", "trusting");
|
|
1532
618
|
|
|
1533
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
619
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1534
620
|
|
|
1535
|
-
const peerState = client.syncManager.peers[nodeToServerPeer.id]!;
|
|
1536
621
|
expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(
|
|
1537
622
|
peerState.knownStates.get(map.core.id),
|
|
1538
623
|
);
|
|
@@ -1553,36 +638,32 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
|
1553
638
|
|
|
1554
639
|
describe("SyncManager.addPeer", () => {
|
|
1555
640
|
test("new peer gets a copy of previous peer's knownStates when replacing it", async () => {
|
|
1556
|
-
const
|
|
641
|
+
const client = await setupTestAccount();
|
|
642
|
+
|
|
643
|
+
const { peerState: firstPeerState, getCurrentPeerState } =
|
|
644
|
+
client.connectToSyncServer();
|
|
1557
645
|
|
|
1558
646
|
// Create test data
|
|
1559
|
-
const group = client.createGroup();
|
|
647
|
+
const group = client.node.createGroup();
|
|
1560
648
|
const map = group.createMap();
|
|
1561
649
|
map.set("key1", "value1", "trusting");
|
|
1562
650
|
|
|
1563
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
651
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1564
652
|
|
|
1565
653
|
// Wait for initial sync
|
|
1566
654
|
await map.core.waitForSync();
|
|
1567
655
|
|
|
1568
|
-
const firstPeerState = client.syncManager.getPeers()[0]!;
|
|
1569
|
-
|
|
1570
656
|
// Store the initial known states
|
|
1571
657
|
const initialKnownStates = firstPeerState.knownStates;
|
|
1572
658
|
|
|
1573
659
|
// Create new connection with same ID
|
|
1574
|
-
|
|
1575
|
-
peer1role: "server",
|
|
1576
|
-
peer2role: "client",
|
|
1577
|
-
});
|
|
1578
|
-
|
|
1579
|
-
// Add new peer with same ID
|
|
1580
|
-
client.syncManager.addPeer(secondPeer);
|
|
660
|
+
client.connectToSyncServer();
|
|
1581
661
|
|
|
1582
|
-
|
|
662
|
+
// Wait for the new peer to be added
|
|
663
|
+
await waitFor(() => expect(getCurrentPeerState()).not.toBe(firstPeerState));
|
|
1583
664
|
|
|
1584
665
|
// Verify that the new peer has a copy of the previous known states
|
|
1585
|
-
const newPeerKnownStates =
|
|
666
|
+
const newPeerKnownStates = getCurrentPeerState().knownStates;
|
|
1586
667
|
|
|
1587
668
|
expect(newPeerKnownStates).not.toBe(initialKnownStates); // Should be a different instance
|
|
1588
669
|
expect(newPeerKnownStates.get(map.core.id)).toEqual(
|
|
@@ -1591,14 +672,16 @@ describe("SyncManager.addPeer", () => {
|
|
|
1591
672
|
});
|
|
1592
673
|
|
|
1593
674
|
test("new peer with new ID starts with empty knownStates", async () => {
|
|
1594
|
-
const
|
|
675
|
+
const client = await setupTestAccount({
|
|
676
|
+
connected: true,
|
|
677
|
+
});
|
|
1595
678
|
|
|
1596
679
|
// Create test data
|
|
1597
|
-
const group = client.createGroup();
|
|
680
|
+
const group = client.node.createGroup();
|
|
1598
681
|
const map = group.createMap();
|
|
1599
682
|
map.set("key1", "value1", "trusting");
|
|
1600
683
|
|
|
1601
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
684
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1602
685
|
|
|
1603
686
|
// Wait for initial sync
|
|
1604
687
|
await map.core.waitForSync();
|
|
@@ -1610,97 +693,112 @@ describe("SyncManager.addPeer", () => {
|
|
|
1610
693
|
});
|
|
1611
694
|
|
|
1612
695
|
// Add new peer with different ID
|
|
1613
|
-
client.syncManager.addPeer(brandNewPeer);
|
|
696
|
+
client.node.syncManager.addPeer(brandNewPeer);
|
|
1614
697
|
|
|
1615
698
|
// Verify that the new peer starts with empty known states
|
|
1616
699
|
const newPeerKnownStates =
|
|
1617
|
-
client.syncManager.peers["brandNewPeer"]!.knownStates;
|
|
700
|
+
client.node.syncManager.peers["brandNewPeer"]!.knownStates;
|
|
1618
701
|
expect(newPeerKnownStates.get(map.core.id)).toBe(undefined);
|
|
1619
702
|
});
|
|
1620
703
|
|
|
1621
704
|
test("when adding a peer with the same ID as a previous peer, the previous peer is closed", async () => {
|
|
1622
|
-
const
|
|
705
|
+
const client = await setupTestAccount({});
|
|
706
|
+
|
|
707
|
+
const { peerState: firstPeerState } = client.connectToSyncServer();
|
|
1623
708
|
|
|
1624
709
|
// Store reference to first peer
|
|
1625
|
-
const
|
|
1626
|
-
const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
|
|
710
|
+
const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
|
|
1627
711
|
|
|
1628
712
|
// Create and add replacement peer
|
|
1629
|
-
const [secondPeer] = connectedPeers(
|
|
713
|
+
const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
|
|
1630
714
|
peer1role: "server",
|
|
1631
715
|
peer2role: "client",
|
|
1632
716
|
});
|
|
1633
717
|
|
|
1634
|
-
client.syncManager.addPeer(secondPeer);
|
|
718
|
+
client.node.syncManager.addPeer(secondPeer);
|
|
1635
719
|
|
|
1636
720
|
// Verify thet the first peer had ben closed correctly
|
|
1637
721
|
expect(closeSpy).toHaveBeenCalled();
|
|
1638
|
-
expect(
|
|
722
|
+
expect(firstPeerState.closed).toBe(true);
|
|
1639
723
|
});
|
|
1640
724
|
|
|
1641
725
|
test("when adding a peer with the same ID as a previous peer and the previous peer is closed, do not attempt to close it again", async () => {
|
|
1642
|
-
const
|
|
726
|
+
const client = await setupTestAccount({
|
|
727
|
+
connected: true,
|
|
728
|
+
});
|
|
1643
729
|
|
|
1644
730
|
// Store reference to first peer
|
|
1645
|
-
const
|
|
731
|
+
const { peerState: firstPeerState } = client.connectToSyncServer();
|
|
1646
732
|
|
|
1647
|
-
|
|
1648
|
-
const closeSpy = vi.spyOn(
|
|
733
|
+
firstPeerState.gracefulShutdown();
|
|
734
|
+
const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
|
|
1649
735
|
|
|
1650
736
|
// Create and add replacement peer
|
|
1651
|
-
const [secondPeer] = connectedPeers(
|
|
737
|
+
const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
|
|
1652
738
|
peer1role: "server",
|
|
1653
739
|
peer2role: "client",
|
|
1654
740
|
});
|
|
1655
741
|
|
|
1656
|
-
client.syncManager.addPeer(secondPeer);
|
|
742
|
+
client.node.syncManager.addPeer(secondPeer);
|
|
1657
743
|
|
|
1658
744
|
// Verify thet the first peer had not been closed again
|
|
1659
745
|
expect(closeSpy).not.toHaveBeenCalled();
|
|
1660
|
-
expect(
|
|
746
|
+
expect(firstPeerState.closed).toBe(true);
|
|
1661
747
|
});
|
|
1662
748
|
|
|
1663
749
|
test("when adding a server peer the local coValues should be sent to it", async () => {
|
|
1664
|
-
const
|
|
750
|
+
const client = await setupTestAccount({
|
|
1665
751
|
connected: false,
|
|
1666
752
|
});
|
|
1667
753
|
|
|
1668
|
-
const group = client.createGroup();
|
|
754
|
+
const group = client.node.createGroup();
|
|
1669
755
|
const map = group.createMap();
|
|
1670
756
|
map.set("key1", "value1", "trusting");
|
|
1671
757
|
|
|
1672
|
-
|
|
758
|
+
client.connectToSyncServer();
|
|
1673
759
|
|
|
1674
760
|
await map.core.waitForSync();
|
|
1675
761
|
|
|
1676
|
-
expect(jazzCloud.coValuesStore.get(map.id).state.type).toBe(
|
|
762
|
+
expect(jazzCloud.node.coValuesStore.get(map.id).state.type).toBe(
|
|
763
|
+
"available",
|
|
764
|
+
);
|
|
1677
765
|
});
|
|
1678
766
|
});
|
|
1679
767
|
|
|
1680
768
|
describe("loadCoValueCore with retry", () => {
|
|
1681
769
|
test("should load the value if available on the server", async () => {
|
|
1682
|
-
const
|
|
1683
|
-
|
|
770
|
+
const client = await setupTestAccount({
|
|
771
|
+
connected: true,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const anotherClient = await setupTestAccount({
|
|
775
|
+
connected: true,
|
|
776
|
+
});
|
|
1684
777
|
|
|
1685
|
-
const group = anotherClient.createGroup();
|
|
778
|
+
const group = anotherClient.node.createGroup();
|
|
1686
779
|
const map = group.createMap();
|
|
1687
780
|
map.set("key1", "value1", "trusting");
|
|
1688
781
|
|
|
1689
|
-
const promise = client.loadCoValueCore(map.id);
|
|
782
|
+
const promise = client.node.loadCoValueCore(map.id);
|
|
1690
783
|
|
|
1691
784
|
await expect(promise).resolves.not.toBe("unavailable");
|
|
1692
785
|
});
|
|
1693
786
|
|
|
1694
787
|
test("should handle correctly two subsequent loads", async () => {
|
|
1695
|
-
const
|
|
1696
|
-
|
|
788
|
+
const client = await setupTestAccount({
|
|
789
|
+
connected: true,
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const anotherClient = await setupTestAccount({
|
|
793
|
+
connected: true,
|
|
794
|
+
});
|
|
1697
795
|
|
|
1698
|
-
const group = anotherClient.createGroup();
|
|
796
|
+
const group = anotherClient.node.createGroup();
|
|
1699
797
|
const map = group.createMap();
|
|
1700
798
|
map.set("key1", "value1", "trusting");
|
|
1701
799
|
|
|
1702
|
-
const promise1 = client.loadCoValueCore(map.id);
|
|
1703
|
-
const promise2 = client.loadCoValueCore(map.id);
|
|
800
|
+
const promise1 = client.node.loadCoValueCore(map.id);
|
|
801
|
+
const promise2 = client.node.loadCoValueCore(map.id);
|
|
1704
802
|
|
|
1705
803
|
await expect(promise1).resolves.not.toBe("unavailable");
|
|
1706
804
|
await expect(promise2).resolves.not.toBe("unavailable");
|
|
@@ -1748,56 +846,58 @@ describe("loadCoValueCore with retry", () => {
|
|
|
1748
846
|
|
|
1749
847
|
describe("waitForSyncWithPeer", () => {
|
|
1750
848
|
test("should resolve when the coValue is fully uploaded into the peer", async () => {
|
|
1751
|
-
const
|
|
849
|
+
const client = await setupTestAccount();
|
|
850
|
+
|
|
851
|
+
const { peerState } = client.connectToSyncServer();
|
|
1752
852
|
|
|
1753
853
|
// Create test data
|
|
1754
|
-
const group = client.createGroup();
|
|
854
|
+
const group = client.node.createGroup();
|
|
1755
855
|
const map = group.createMap();
|
|
1756
856
|
map.set("key1", "value1", "trusting");
|
|
1757
857
|
|
|
1758
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
1759
|
-
|
|
1760
|
-
const peer = client.syncManager.getPeers()[0];
|
|
1761
|
-
|
|
1762
|
-
if (!peer) {
|
|
1763
|
-
throw new Error("No peer found");
|
|
1764
|
-
}
|
|
858
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1765
859
|
|
|
1766
860
|
await expect(
|
|
1767
|
-
client.syncManager.waitForSyncWithPeer(
|
|
861
|
+
client.node.syncManager.waitForSyncWithPeer(
|
|
862
|
+
peerState.id,
|
|
863
|
+
map.core.id,
|
|
864
|
+
100,
|
|
865
|
+
),
|
|
1768
866
|
).resolves.toBe(true);
|
|
1769
867
|
});
|
|
1770
868
|
|
|
1771
869
|
test("should not resolve when the coValue is not synced", async () => {
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
const peer = client.syncManager.getPeers()[0];
|
|
870
|
+
const client = await setupTestAccount();
|
|
1775
871
|
|
|
1776
|
-
|
|
1777
|
-
throw new Error("No peer found");
|
|
1778
|
-
}
|
|
872
|
+
const { peerState } = client.connectToSyncServer();
|
|
1779
873
|
|
|
1780
874
|
// Create test data
|
|
1781
|
-
const group = client.createGroup();
|
|
875
|
+
const group = client.node.createGroup();
|
|
1782
876
|
const map = group.createMap();
|
|
1783
877
|
map.set("key1", "value1", "trusting");
|
|
1784
878
|
|
|
1785
|
-
vi.spyOn(
|
|
879
|
+
vi.spyOn(peerState, "pushOutgoingMessage").mockImplementation(async () => {
|
|
1786
880
|
return Promise.resolve();
|
|
1787
881
|
});
|
|
1788
882
|
|
|
1789
|
-
await client.syncManager.actuallySyncCoValue(map.core);
|
|
883
|
+
await client.node.syncManager.actuallySyncCoValue(map.core);
|
|
1790
884
|
|
|
1791
885
|
await expect(
|
|
1792
|
-
client.syncManager.waitForSyncWithPeer(
|
|
886
|
+
client.node.syncManager.waitForSyncWithPeer(
|
|
887
|
+
peerState.id,
|
|
888
|
+
map.core.id,
|
|
889
|
+
100,
|
|
890
|
+
),
|
|
1793
891
|
).rejects.toThrow("Timeout");
|
|
1794
892
|
});
|
|
1795
893
|
});
|
|
1796
894
|
|
|
1797
895
|
test("Should not crash when syncing an unknown coValue type", async () => {
|
|
1798
|
-
const
|
|
896
|
+
const client = await setupTestAccount({
|
|
897
|
+
connected: true,
|
|
898
|
+
});
|
|
1799
899
|
|
|
1800
|
-
const coValue = client.createCoValue({
|
|
900
|
+
const coValue = client.node.createCoValue({
|
|
1801
901
|
type: "ooops" as any,
|
|
1802
902
|
ruleset: { type: "unsafeAllowAll" },
|
|
1803
903
|
meta: null,
|
|
@@ -1806,10 +906,12 @@ test("Should not crash when syncing an unknown coValue type", async () => {
|
|
|
1806
906
|
|
|
1807
907
|
await coValue.waitForSync();
|
|
1808
908
|
|
|
1809
|
-
const
|
|
909
|
+
const anotherClient = await setupTestAccount({
|
|
910
|
+
connected: true,
|
|
911
|
+
});
|
|
1810
912
|
|
|
1811
913
|
const coValueOnTheOtherNode = await loadCoValueOrFail(
|
|
1812
|
-
anotherClient,
|
|
914
|
+
anotherClient.node,
|
|
1813
915
|
coValue.getCurrentContent().id,
|
|
1814
916
|
);
|
|
1815
917
|
expect(coValueOnTheOtherNode.id).toBe(coValue.id);
|
|
@@ -1903,188 +1005,6 @@ describe("metrics", () => {
|
|
|
1903
1005
|
});
|
|
1904
1006
|
});
|
|
1905
1007
|
|
|
1906
|
-
describe("sync protocol", () => {
|
|
1907
|
-
test("should have the correct messages exchanged between client and server", async () => {
|
|
1908
|
-
// Creating the account from agent to simplify the messages exchange
|
|
1909
|
-
const { node: client, messages } = await createConnectedTestAgentNode();
|
|
1910
|
-
|
|
1911
|
-
const group = client.createGroup();
|
|
1912
|
-
const map = group.createMap();
|
|
1913
|
-
map.set("hello", "world", "trusting");
|
|
1914
|
-
|
|
1915
|
-
await map.core.waitForSync();
|
|
1916
|
-
|
|
1917
|
-
const mapOnJazzCloud = await loadCoValueOrFail(jazzCloud, map.id);
|
|
1918
|
-
expect(mapOnJazzCloud.get("hello")).toEqual("world");
|
|
1919
|
-
|
|
1920
|
-
expect(messages).toEqual([
|
|
1921
|
-
{
|
|
1922
|
-
from: "client",
|
|
1923
|
-
msg: {
|
|
1924
|
-
action: "load",
|
|
1925
|
-
header: true,
|
|
1926
|
-
id: group.id,
|
|
1927
|
-
sessions: {
|
|
1928
|
-
[client.currentSessionID]: 3,
|
|
1929
|
-
},
|
|
1930
|
-
},
|
|
1931
|
-
},
|
|
1932
|
-
{
|
|
1933
|
-
from: "server",
|
|
1934
|
-
msg: {
|
|
1935
|
-
action: "load",
|
|
1936
|
-
header: false,
|
|
1937
|
-
id: group.id,
|
|
1938
|
-
sessions: {},
|
|
1939
|
-
},
|
|
1940
|
-
},
|
|
1941
|
-
{
|
|
1942
|
-
from: "client",
|
|
1943
|
-
msg: {
|
|
1944
|
-
action: "load",
|
|
1945
|
-
header: true,
|
|
1946
|
-
id: map.id,
|
|
1947
|
-
sessions: {
|
|
1948
|
-
[client.currentSessionID]: 1,
|
|
1949
|
-
},
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
{
|
|
1953
|
-
from: "server",
|
|
1954
|
-
msg: {
|
|
1955
|
-
action: "load",
|
|
1956
|
-
header: false,
|
|
1957
|
-
id: map.id,
|
|
1958
|
-
sessions: {},
|
|
1959
|
-
},
|
|
1960
|
-
},
|
|
1961
|
-
{
|
|
1962
|
-
from: "client",
|
|
1963
|
-
msg: {
|
|
1964
|
-
action: "content",
|
|
1965
|
-
header: {
|
|
1966
|
-
createdAt: expect.any(String),
|
|
1967
|
-
meta: null,
|
|
1968
|
-
ruleset: {
|
|
1969
|
-
initialAdmin: client.account.id,
|
|
1970
|
-
type: "group",
|
|
1971
|
-
},
|
|
1972
|
-
type: "comap",
|
|
1973
|
-
uniqueness: expect.any(String),
|
|
1974
|
-
},
|
|
1975
|
-
id: group.id,
|
|
1976
|
-
new: {
|
|
1977
|
-
[client.currentSessionID]: {
|
|
1978
|
-
after: 0,
|
|
1979
|
-
lastSignature: expect.any(String),
|
|
1980
|
-
newTransactions: expect.any(Array),
|
|
1981
|
-
},
|
|
1982
|
-
},
|
|
1983
|
-
priority: 0,
|
|
1984
|
-
},
|
|
1985
|
-
},
|
|
1986
|
-
{
|
|
1987
|
-
from: "client",
|
|
1988
|
-
msg: {
|
|
1989
|
-
action: "content",
|
|
1990
|
-
header: {
|
|
1991
|
-
createdAt: expect.any(String),
|
|
1992
|
-
meta: null,
|
|
1993
|
-
ruleset: {
|
|
1994
|
-
group: group.id,
|
|
1995
|
-
type: "ownedByGroup",
|
|
1996
|
-
},
|
|
1997
|
-
type: "comap",
|
|
1998
|
-
uniqueness: expect.any(String),
|
|
1999
|
-
},
|
|
2000
|
-
id: map.id,
|
|
2001
|
-
new: {
|
|
2002
|
-
[client.currentSessionID]: {
|
|
2003
|
-
after: 0,
|
|
2004
|
-
lastSignature: expect.any(String),
|
|
2005
|
-
newTransactions: expect.any(Array),
|
|
2006
|
-
},
|
|
2007
|
-
},
|
|
2008
|
-
priority: 3,
|
|
2009
|
-
},
|
|
2010
|
-
},
|
|
2011
|
-
{
|
|
2012
|
-
from: "server",
|
|
2013
|
-
msg: {
|
|
2014
|
-
action: "known",
|
|
2015
|
-
header: true,
|
|
2016
|
-
id: group.id,
|
|
2017
|
-
sessions: {
|
|
2018
|
-
[client.currentSessionID]: 3,
|
|
2019
|
-
},
|
|
2020
|
-
},
|
|
2021
|
-
},
|
|
2022
|
-
{
|
|
2023
|
-
// TODO: This is a redundant message, we should remove it
|
|
2024
|
-
from: "client",
|
|
2025
|
-
msg: {
|
|
2026
|
-
action: "content",
|
|
2027
|
-
header: {
|
|
2028
|
-
createdAt: expect.any(String),
|
|
2029
|
-
meta: null,
|
|
2030
|
-
ruleset: {
|
|
2031
|
-
group: group.id,
|
|
2032
|
-
type: "ownedByGroup",
|
|
2033
|
-
},
|
|
2034
|
-
type: "comap",
|
|
2035
|
-
uniqueness: expect.any(String),
|
|
2036
|
-
},
|
|
2037
|
-
id: map.id,
|
|
2038
|
-
new: {
|
|
2039
|
-
[client.currentSessionID]: {
|
|
2040
|
-
after: 0,
|
|
2041
|
-
lastSignature: expect.any(String),
|
|
2042
|
-
newTransactions: expect.any(Array),
|
|
2043
|
-
},
|
|
2044
|
-
},
|
|
2045
|
-
priority: 3,
|
|
2046
|
-
},
|
|
2047
|
-
},
|
|
2048
|
-
{
|
|
2049
|
-
// TODO: This is a redundant message, we should remove it
|
|
2050
|
-
from: "server",
|
|
2051
|
-
msg: {
|
|
2052
|
-
action: "known",
|
|
2053
|
-
asDependencyOf: undefined,
|
|
2054
|
-
header: true,
|
|
2055
|
-
id: group.id,
|
|
2056
|
-
sessions: {
|
|
2057
|
-
[client.currentSessionID]: 3,
|
|
2058
|
-
},
|
|
2059
|
-
},
|
|
2060
|
-
},
|
|
2061
|
-
{
|
|
2062
|
-
from: "server",
|
|
2063
|
-
msg: {
|
|
2064
|
-
action: "known",
|
|
2065
|
-
header: true,
|
|
2066
|
-
id: map.id,
|
|
2067
|
-
sessions: {
|
|
2068
|
-
[client.currentSessionID]: 1,
|
|
2069
|
-
},
|
|
2070
|
-
},
|
|
2071
|
-
},
|
|
2072
|
-
{
|
|
2073
|
-
from: "server",
|
|
2074
|
-
msg: {
|
|
2075
|
-
action: "known",
|
|
2076
|
-
asDependencyOf: undefined,
|
|
2077
|
-
header: true,
|
|
2078
|
-
id: map.id,
|
|
2079
|
-
sessions: {
|
|
2080
|
-
[client.currentSessionID]: 1,
|
|
2081
|
-
},
|
|
2082
|
-
},
|
|
2083
|
-
},
|
|
2084
|
-
]);
|
|
2085
|
-
});
|
|
2086
|
-
});
|
|
2087
|
-
|
|
2088
1008
|
function groupContentEx(group: RawGroup) {
|
|
2089
1009
|
return {
|
|
2090
1010
|
action: "content",
|
|
@@ -2101,19 +1021,19 @@ function groupStateEx(group: RawGroup) {
|
|
|
2101
1021
|
|
|
2102
1022
|
describe("LocalNode.load", () => {
|
|
2103
1023
|
test("should throw error when trying to load with undefined ID", async () => {
|
|
2104
|
-
const
|
|
1024
|
+
const client = await setupTestAccount();
|
|
2105
1025
|
|
|
2106
1026
|
// @ts-expect-error Testing with undefined ID
|
|
2107
|
-
await expect(node.load(undefined)).rejects.toThrow(
|
|
1027
|
+
await expect(client.node.load(undefined)).rejects.toThrow(
|
|
2108
1028
|
"Trying to load CoValue with undefined id",
|
|
2109
1029
|
);
|
|
2110
1030
|
});
|
|
2111
1031
|
|
|
2112
1032
|
test("should throw error when trying to load with invalid ID format", async () => {
|
|
2113
|
-
const
|
|
1033
|
+
const client = await setupTestAccount();
|
|
2114
1034
|
|
|
2115
1035
|
// @ts-expect-error Testing with invalid ID format
|
|
2116
|
-
await expect(node.load("invalid_id")).rejects.toThrow(
|
|
1036
|
+
await expect(client.node.load("invalid_id")).rejects.toThrow(
|
|
2117
1037
|
"Trying to load CoValue with invalid id invalid_id",
|
|
2118
1038
|
);
|
|
2119
1039
|
});
|
|
@@ -2121,8 +1041,9 @@ describe("LocalNode.load", () => {
|
|
|
2121
1041
|
|
|
2122
1042
|
describe("SyncManager.handleSyncMessage", () => {
|
|
2123
1043
|
test("should ignore messages with undefined ID", async () => {
|
|
2124
|
-
const
|
|
2125
|
-
|
|
1044
|
+
const client = await setupTestAccount();
|
|
1045
|
+
|
|
1046
|
+
const { peerState } = client.connectToSyncServer();
|
|
2126
1047
|
|
|
2127
1048
|
// Create an invalid message with undefined ID
|
|
2128
1049
|
const invalidMessage = {
|
|
@@ -2132,16 +1053,17 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
2132
1053
|
sessions: {},
|
|
2133
1054
|
} as unknown as LoadMessage;
|
|
2134
1055
|
|
|
2135
|
-
await client.syncManager.handleSyncMessage(invalidMessage,
|
|
1056
|
+
await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
|
|
2136
1057
|
|
|
2137
1058
|
// Verify that no state changes occurred
|
|
2138
|
-
expect(
|
|
2139
|
-
expect(
|
|
1059
|
+
expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
|
|
1060
|
+
expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
|
|
2140
1061
|
});
|
|
2141
1062
|
|
|
2142
1063
|
test("should ignore messages with invalid ID format", async () => {
|
|
2143
|
-
const
|
|
2144
|
-
|
|
1064
|
+
const client = await setupTestAccount();
|
|
1065
|
+
|
|
1066
|
+
const { peerState } = client.connectToSyncServer();
|
|
2145
1067
|
|
|
2146
1068
|
// Create an invalid message with wrong ID format
|
|
2147
1069
|
const invalidMessage = {
|
|
@@ -2151,20 +1073,21 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
2151
1073
|
sessions: {},
|
|
2152
1074
|
} as unknown as LoadMessage;
|
|
2153
1075
|
|
|
2154
|
-
await client.syncManager.handleSyncMessage(invalidMessage,
|
|
1076
|
+
await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
|
|
2155
1077
|
|
|
2156
1078
|
// Verify that no state changes occurred
|
|
2157
|
-
expect(
|
|
2158
|
-
expect(
|
|
1079
|
+
expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
|
|
1080
|
+
expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
|
|
2159
1081
|
});
|
|
2160
1082
|
|
|
2161
1083
|
test("should ignore messages for errored coValues", async () => {
|
|
2162
|
-
const
|
|
2163
|
-
|
|
1084
|
+
const client = await setupTestAccount();
|
|
1085
|
+
|
|
1086
|
+
const { peerState } = client.connectToSyncServer();
|
|
2164
1087
|
|
|
2165
1088
|
// Add a coValue to the errored set
|
|
2166
1089
|
const erroredId = "co_z123" as const;
|
|
2167
|
-
|
|
1090
|
+
peerState.erroredCoValues.set(
|
|
2168
1091
|
erroredId,
|
|
2169
1092
|
new Error("Test error") as unknown as TryAddTransactionsError,
|
|
2170
1093
|
);
|
|
@@ -2176,17 +1099,19 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
2176
1099
|
sessions: {},
|
|
2177
1100
|
} satisfies LoadMessage;
|
|
2178
1101
|
|
|
2179
|
-
await client.syncManager.handleSyncMessage(message,
|
|
1102
|
+
await client.node.syncManager.handleSyncMessage(message, peerState);
|
|
2180
1103
|
|
|
2181
1104
|
// Verify that no state changes occurred
|
|
2182
|
-
expect(
|
|
2183
|
-
expect(
|
|
1105
|
+
expect(peerState.knownStates.has(message.id)).toBe(false);
|
|
1106
|
+
expect(peerState.optimisticKnownStates.has(message.id)).toBe(false);
|
|
2184
1107
|
});
|
|
2185
1108
|
|
|
2186
1109
|
test("should process valid messages", async () => {
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
const
|
|
1110
|
+
const client = await setupTestAccount();
|
|
1111
|
+
|
|
1112
|
+
const { peerState } = client.connectToSyncServer();
|
|
1113
|
+
|
|
1114
|
+
const group = client.node.createGroup();
|
|
2190
1115
|
|
|
2191
1116
|
const validMessage = {
|
|
2192
1117
|
action: "load" as const,
|
|
@@ -2195,9 +1120,9 @@ describe("SyncManager.handleSyncMessage", () => {
|
|
|
2195
1120
|
sessions: {},
|
|
2196
1121
|
};
|
|
2197
1122
|
|
|
2198
|
-
await client.syncManager.handleSyncMessage(validMessage,
|
|
1123
|
+
await client.node.syncManager.handleSyncMessage(validMessage, peerState);
|
|
2199
1124
|
|
|
2200
1125
|
// Verify that the message was processed
|
|
2201
|
-
expect(
|
|
1126
|
+
expect(peerState.knownStates.has(group.id)).toBe(true);
|
|
2202
1127
|
});
|
|
2203
1128
|
});
|