cojson 0.13.2 → 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.
Files changed (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/LICENSE.txt +1 -1
  4. package/dist/PeerState.d.ts +6 -0
  5. package/dist/PeerState.d.ts.map +1 -1
  6. package/dist/PeerState.js +43 -0
  7. package/dist/PeerState.js.map +1 -1
  8. package/dist/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore.js +1 -0
  10. package/dist/coValueCore.js.map +1 -1
  11. package/dist/coValueState.d.ts +1 -0
  12. package/dist/coValueState.d.ts.map +1 -1
  13. package/dist/coValueState.js +27 -2
  14. package/dist/coValueState.js.map +1 -1
  15. package/dist/coValues/group.d.ts +1 -0
  16. package/dist/coValues/group.d.ts.map +1 -1
  17. package/dist/coValues/group.js +45 -21
  18. package/dist/coValues/group.js.map +1 -1
  19. package/dist/crypto/crypto.d.ts +2 -2
  20. package/dist/crypto/crypto.d.ts.map +1 -1
  21. package/dist/permissions.d.ts +1 -0
  22. package/dist/permissions.d.ts.map +1 -1
  23. package/dist/permissions.js +19 -3
  24. package/dist/permissions.js.map +1 -1
  25. package/dist/storage/FileSystem.d.ts +2 -2
  26. package/dist/storage/FileSystem.d.ts.map +1 -1
  27. package/dist/sync.d.ts +14 -4
  28. package/dist/sync.d.ts.map +1 -1
  29. package/dist/sync.js +146 -146
  30. package/dist/sync.js.map +1 -1
  31. package/dist/tests/SyncStateManager.test.js +51 -46
  32. package/dist/tests/SyncStateManager.test.js.map +1 -1
  33. package/dist/tests/coValueCore.test.js +51 -2
  34. package/dist/tests/coValueCore.test.js.map +1 -1
  35. package/dist/tests/coValueState.test.js +31 -4
  36. package/dist/tests/coValueState.test.js.map +1 -1
  37. package/dist/tests/group.test.js +135 -2
  38. package/dist/tests/group.test.js.map +1 -1
  39. package/dist/tests/messagesTestUtils.d.ts +13 -0
  40. package/dist/tests/messagesTestUtils.d.ts.map +1 -0
  41. package/dist/tests/messagesTestUtils.js +42 -0
  42. package/dist/tests/messagesTestUtils.js.map +1 -0
  43. package/dist/tests/sync.load.test.d.ts +2 -0
  44. package/dist/tests/sync.load.test.d.ts.map +1 -0
  45. package/dist/tests/sync.load.test.js +249 -0
  46. package/dist/tests/sync.load.test.js.map +1 -0
  47. package/dist/tests/sync.mesh.test.d.ts +2 -0
  48. package/dist/tests/sync.mesh.test.d.ts.map +1 -0
  49. package/dist/tests/sync.mesh.test.js +157 -0
  50. package/dist/tests/sync.mesh.test.js.map +1 -0
  51. package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
  52. package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
  53. package/dist/tests/sync.peerReconciliation.test.js +130 -0
  54. package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
  55. package/dist/tests/sync.storage.test.d.ts +2 -0
  56. package/dist/tests/sync.storage.test.d.ts.map +1 -0
  57. package/dist/tests/sync.storage.test.js +201 -0
  58. package/dist/tests/sync.storage.test.js.map +1 -0
  59. package/dist/tests/sync.test.js +139 -1048
  60. package/dist/tests/sync.test.js.map +1 -1
  61. package/dist/tests/sync.upload.test.d.ts +2 -0
  62. package/dist/tests/sync.upload.test.d.ts.map +1 -0
  63. package/dist/tests/sync.upload.test.js +156 -0
  64. package/dist/tests/sync.upload.test.js.map +1 -0
  65. package/dist/tests/testUtils.d.ts +76 -33
  66. package/dist/tests/testUtils.d.ts.map +1 -1
  67. package/dist/tests/testUtils.js +153 -47
  68. package/dist/tests/testUtils.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/PeerState.ts +59 -1
  71. package/src/coValueCore.ts +1 -0
  72. package/src/coValueState.ts +34 -3
  73. package/src/coValues/group.ts +83 -45
  74. package/src/permissions.ts +31 -3
  75. package/src/sync.ts +169 -185
  76. package/src/tests/SyncStateManager.test.ts +58 -70
  77. package/src/tests/coValueCore.test.ts +70 -1
  78. package/src/tests/coValueState.test.ts +59 -5
  79. package/src/tests/group.test.ts +250 -2
  80. package/src/tests/messagesTestUtils.ts +75 -0
  81. package/src/tests/sync.load.test.ts +327 -0
  82. package/src/tests/sync.mesh.test.ts +219 -0
  83. package/src/tests/sync.peerReconciliation.test.ts +201 -0
  84. package/src/tests/sync.storage.test.ts +259 -0
  85. package/src/tests/sync.test.ts +170 -1245
  86. package/src/tests/sync.upload.test.ts +202 -0
  87. package/src/tests/testUtils.ts +213 -61
@@ -1,4 +1,12 @@
1
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
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
- setupSyncServer,
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 = setupSyncServer();
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
- test("Node replies with only new tx to subscribe with some known state", async () => {
117
- const [admin, session] = randomAnonymousAccountAndSessionID();
118
- const node = new LocalNode(admin, session, Crypto);
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 createConnectedTestNode();
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 createConnectedTestNode();
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 createConnectedTestNode();
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 createConnectedTestNode();
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 = createTestNode();
120
+ const client = setupTestNode();
961
121
 
962
- const { nodeToServerPeer, serverToNodePeer } =
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 serverToNodePeer.outgoing.push("Disconnected");
133
+ await peer.outgoing.push("Disconnected");
976
134
 
977
135
  await waitFor(() => peerState?.closed);
978
136
 
979
- expect(syncManager.peers[nodeToServerPeer.id]).not.toBeUndefined();
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 = createTestNode();
141
+ const client = setupTestNode();
984
142
 
985
- const { nodeToServerPeer, serverToNodePeer } =
986
- connectNodeToSyncServer(client);
143
+ const { peer, peerState } = client.connectToSyncServer();
987
144
 
988
- nodeToServerPeer.deletePeerStateOnClose = true;
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 serverToNodePeer.outgoing.push("Disconnected");
156
+ await peer.outgoing.push("Disconnected");
1002
157
 
1003
158
  await waitFor(() => peerState?.closed);
1004
159
 
1005
- expect(syncManager.peers[nodeToServerPeer.id]).toBeUndefined();
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 createConnectedTestNode();
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 { node: client, nodeToServerPeer } = await createConnectedTestNode();
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 { node: client } = await createConnectedTestNode();
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
- const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
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
- const newPeerState = client.syncManager.getPeers()[0]!;
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 = newPeerState.knownStates;
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 { node: client } = await createConnectedTestNode();
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 { node: client } = await createConnectedTestNode();
705
+ const client = await setupTestAccount({});
706
+
707
+ const { peerState: firstPeerState } = client.connectToSyncServer();
1623
708
 
1624
709
  // Store reference to first peer
1625
- const firstPeer = client.syncManager.getPeers()[0]!;
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(firstPeer.id, "unusedPeer", {
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(firstPeer.closed).toBe(true);
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 { node: client } = await createConnectedTestNode();
726
+ const client = await setupTestAccount({
727
+ connected: true,
728
+ });
1643
729
 
1644
730
  // Store reference to first peer
1645
- const firstPeer = client.syncManager.getPeers()[0]!;
731
+ const { peerState: firstPeerState } = client.connectToSyncServer();
1646
732
 
1647
- firstPeer.gracefulShutdown();
1648
- const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
733
+ firstPeerState.gracefulShutdown();
734
+ const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
1649
735
 
1650
736
  // Create and add replacement peer
1651
- const [secondPeer] = connectedPeers(firstPeer.id, "unusedPeer", {
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(firstPeer.closed).toBe(true);
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 { node: client, addServerPeer } = await createConnectedTestNode({
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
- addServerPeer();
758
+ client.connectToSyncServer();
1673
759
 
1674
760
  await map.core.waitForSync();
1675
761
 
1676
- expect(jazzCloud.coValuesStore.get(map.id).state.type).toBe("available");
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 { node: client } = await createConnectedTestNode();
1683
- const { node: anotherClient } = await createConnectedTestNode();
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 { node: client } = await createConnectedTestNode();
1696
- const { node: anotherClient } = await createConnectedTestNode();
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 { node: client } = await createConnectedTestNode();
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(peer.id, map.core.id, 100),
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 { node: client } = await createConnectedTestNode();
1773
-
1774
- const peer = client.syncManager.getPeers()[0];
870
+ const client = await setupTestAccount();
1775
871
 
1776
- if (!peer) {
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(peer, "pushOutgoingMessage").mockImplementation(async () => {
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(peer.id, map.core.id, 100),
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 { node: client } = await createConnectedTestNode();
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 { node: anotherClient } = await createConnectedTestNode();
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 { node } = await createConnectedTestNode();
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 { node } = await createConnectedTestNode();
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 { node: client } = await createConnectedTestNode();
2125
- const peer = client.syncManager.getPeers()[0]!;
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, peer);
1056
+ await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
2136
1057
 
2137
1058
  // Verify that no state changes occurred
2138
- expect(peer.knownStates.has(invalidMessage.id)).toBe(false);
2139
- expect(peer.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
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 { node: client } = await createConnectedTestNode();
2144
- const peer = client.syncManager.getPeers()[0]!;
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, peer);
1076
+ await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
2155
1077
 
2156
1078
  // Verify that no state changes occurred
2157
- expect(peer.knownStates.has(invalidMessage.id)).toBe(false);
2158
- expect(peer.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
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 { node: client } = await createConnectedTestNode();
2163
- const peer = client.syncManager.getPeers()[0]!;
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
- peer.erroredCoValues.set(
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, peer);
1102
+ await client.node.syncManager.handleSyncMessage(message, peerState);
2180
1103
 
2181
1104
  // Verify that no state changes occurred
2182
- expect(peer.knownStates.has(message.id)).toBe(false);
2183
- expect(peer.optimisticKnownStates.has(message.id)).toBe(false);
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 { node: client } = await createConnectedTestNode();
2188
- const group = client.createGroup();
2189
- const peer = client.syncManager.getPeers()[0]!;
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, peer);
1123
+ await client.node.syncManager.handleSyncMessage(validMessage, peerState);
2199
1124
 
2200
1125
  // Verify that the message was processed
2201
- expect(peer.knownStates.has(group.id)).toBe(true);
1126
+ expect(peerState.knownStates.has(group.id)).toBe(true);
2202
1127
  });
2203
1128
  });