cojson 0.13.5 → 0.13.10

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 (88) 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 +3 -1
  9. package/dist/coValueCore.d.ts.map +1 -1
  10. package/dist/coValueCore.js +26 -8
  11. package/dist/coValueCore.js.map +1 -1
  12. package/dist/coValueState.d.ts +1 -0
  13. package/dist/coValueState.d.ts.map +1 -1
  14. package/dist/coValueState.js +27 -2
  15. package/dist/coValueState.js.map +1 -1
  16. package/dist/coValues/group.d.ts +1 -0
  17. package/dist/coValues/group.d.ts.map +1 -1
  18. package/dist/coValues/group.js +45 -21
  19. package/dist/coValues/group.js.map +1 -1
  20. package/dist/crypto/crypto.d.ts +2 -2
  21. package/dist/crypto/crypto.d.ts.map +1 -1
  22. package/dist/permissions.d.ts +1 -0
  23. package/dist/permissions.d.ts.map +1 -1
  24. package/dist/permissions.js +19 -3
  25. package/dist/permissions.js.map +1 -1
  26. package/dist/storage/FileSystem.d.ts +2 -2
  27. package/dist/storage/FileSystem.d.ts.map +1 -1
  28. package/dist/sync.d.ts +14 -4
  29. package/dist/sync.d.ts.map +1 -1
  30. package/dist/sync.js +146 -146
  31. package/dist/sync.js.map +1 -1
  32. package/dist/tests/SyncStateManager.test.js +51 -46
  33. package/dist/tests/SyncStateManager.test.js.map +1 -1
  34. package/dist/tests/coValueCore.test.js +66 -4
  35. package/dist/tests/coValueCore.test.js.map +1 -1
  36. package/dist/tests/coValueState.test.js +31 -4
  37. package/dist/tests/coValueState.test.js.map +1 -1
  38. package/dist/tests/group.test.js +135 -2
  39. package/dist/tests/group.test.js.map +1 -1
  40. package/dist/tests/messagesTestUtils.d.ts +13 -0
  41. package/dist/tests/messagesTestUtils.d.ts.map +1 -0
  42. package/dist/tests/messagesTestUtils.js +42 -0
  43. package/dist/tests/messagesTestUtils.js.map +1 -0
  44. package/dist/tests/sync.load.test.d.ts +2 -0
  45. package/dist/tests/sync.load.test.d.ts.map +1 -0
  46. package/dist/tests/sync.load.test.js +249 -0
  47. package/dist/tests/sync.load.test.js.map +1 -0
  48. package/dist/tests/sync.mesh.test.d.ts +2 -0
  49. package/dist/tests/sync.mesh.test.d.ts.map +1 -0
  50. package/dist/tests/sync.mesh.test.js +157 -0
  51. package/dist/tests/sync.mesh.test.js.map +1 -0
  52. package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
  53. package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
  54. package/dist/tests/sync.peerReconciliation.test.js +166 -0
  55. package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
  56. package/dist/tests/sync.storage.test.d.ts +2 -0
  57. package/dist/tests/sync.storage.test.d.ts.map +1 -0
  58. package/dist/tests/sync.storage.test.js +201 -0
  59. package/dist/tests/sync.storage.test.js.map +1 -0
  60. package/dist/tests/sync.test.js +139 -1048
  61. package/dist/tests/sync.test.js.map +1 -1
  62. package/dist/tests/sync.upload.test.d.ts +2 -0
  63. package/dist/tests/sync.upload.test.d.ts.map +1 -0
  64. package/dist/tests/sync.upload.test.js +156 -0
  65. package/dist/tests/sync.upload.test.js.map +1 -0
  66. package/dist/tests/testUtils.d.ts +76 -33
  67. package/dist/tests/testUtils.d.ts.map +1 -1
  68. package/dist/tests/testUtils.js +154 -47
  69. package/dist/tests/testUtils.js.map +1 -1
  70. package/package.json +2 -2
  71. package/src/PeerState.ts +59 -1
  72. package/src/coValueCore.ts +37 -7
  73. package/src/coValueState.ts +34 -3
  74. package/src/coValues/group.ts +83 -45
  75. package/src/permissions.ts +31 -3
  76. package/src/sync.ts +169 -185
  77. package/src/tests/SyncStateManager.test.ts +58 -70
  78. package/src/tests/coValueCore.test.ts +90 -3
  79. package/src/tests/coValueState.test.ts +59 -5
  80. package/src/tests/group.test.ts +250 -2
  81. package/src/tests/messagesTestUtils.ts +75 -0
  82. package/src/tests/sync.load.test.ts +327 -0
  83. package/src/tests/sync.mesh.test.ts +219 -0
  84. package/src/tests/sync.peerReconciliation.test.ts +251 -0
  85. package/src/tests/sync.storage.test.ts +259 -0
  86. package/src/tests/sync.test.ts +170 -1245
  87. package/src/tests/sync.upload.test.ts +202 -0
  88. package/src/tests/testUtils.ts +215 -61
@@ -1,388 +1,17 @@
1
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest";
2
2
  import { expectMap } from "../coValue.js";
3
3
  import { RawCoMap } from "../coValues/coMap.js";
4
4
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
5
- import { stableStringify } from "../jsonStringify.js";
6
5
  import { LocalNode } from "../localNode.js";
7
- import { getPriorityFromHeader } from "../priority.js";
8
6
  import { connectedPeers, newQueuePair } from "../streamUtils.js";
9
- import { blockMessageTypeOnOutgoingPeer, connectNodeToSyncServer, connectTwoPeers, createConnectedTestAgentNode, createConnectedTestNode, createTestMetricReader, createTestNode, loadCoValueOrFail, randomAnonymousAccountAndSessionID, setupSyncServer, tearDownTestMetricReader, waitFor, } from "./testUtils.js";
7
+ import { blockMessageTypeOnOutgoingPeer, connectTwoPeers, createTestMetricReader, createTestNode, loadCoValueOrFail, randomAnonymousAccountAndSessionID, setupTestAccount, setupTestNode, tearDownTestMetricReader, waitFor, } from "./testUtils.js";
10
8
  const Crypto = await WasmCrypto.create();
11
- let jazzCloud = setupSyncServer();
12
- beforeEach(async () => {
13
- jazzCloud = setupSyncServer();
14
- });
15
- test("Node replies with initial tx and header to empty subscribe", async () => {
16
- const [admin, session] = randomAnonymousAccountAndSessionID();
17
- const node = new LocalNode(admin, session, Crypto);
18
- const group = node.createGroup();
19
- const map = group.createMap();
20
- map.set("hello", "world", "trusting");
21
- const [inRx, inTx] = newQueuePair();
22
- const [outRx, outTx] = newQueuePair();
23
- const outRxQ = outRx[Symbol.asyncIterator]();
24
- node.syncManager.addPeer({
25
- id: "test",
26
- incoming: inRx,
27
- outgoing: outTx,
28
- role: "peer",
29
- crashOnClose: true,
30
- });
31
- await inTx.push({
32
- action: "load",
33
- id: map.core.id,
34
- header: false,
35
- sessions: {},
36
- });
37
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
38
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
39
- const mapTellKnownStateMsg = (await outRxQ.next()).value;
40
- expect(mapTellKnownStateMsg).toEqual({
41
- action: "known",
42
- ...map.core.knownState(),
43
- });
44
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
45
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
46
- const newContentMsg = (await outRxQ.next()).value;
47
- const expectedHeader = {
48
- type: "comap",
49
- ruleset: { type: "ownedByGroup", group: group.id },
50
- meta: null,
51
- createdAt: map.core.header.createdAt,
52
- uniqueness: map.core.header.uniqueness,
53
- };
54
- expect(newContentMsg).toEqual({
55
- action: "content",
56
- id: map.core.id,
57
- header: expectedHeader,
58
- new: {
59
- [node.currentSessionID]: {
60
- after: 0,
61
- newTransactions: [
62
- {
63
- privacy: "trusting",
64
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
65
- .transactions[0].madeAt,
66
- changes: stableStringify([
67
- {
68
- op: "set",
69
- key: "hello",
70
- value: "world",
71
- },
72
- ]),
73
- },
74
- ],
75
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
76
- .lastSignature,
77
- },
78
- },
79
- priority: getPriorityFromHeader(map.core.header),
80
- });
81
- });
82
- test("Node replies with only new tx to subscribe with some known state", async () => {
83
- const [admin, session] = randomAnonymousAccountAndSessionID();
84
- const node = new LocalNode(admin, session, Crypto);
85
- const group = node.createGroup();
86
- const map = group.createMap();
87
- map.set("hello", "world", "trusting");
88
- map.set("goodbye", "world", "trusting");
89
- const [inRx, inTx] = newQueuePair();
90
- const [outRx, outTx] = newQueuePair();
91
- const outRxQ = outRx[Symbol.asyncIterator]();
92
- node.syncManager.addPeer({
93
- id: "test",
94
- incoming: inRx,
95
- outgoing: outTx,
96
- role: "peer",
97
- crashOnClose: true,
98
- });
99
- await inTx.push({
100
- action: "load",
101
- id: map.core.id,
102
- header: true,
103
- sessions: {
104
- [node.currentSessionID]: 1,
105
- },
106
- });
107
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
108
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
109
- const mapTellKnownStateMsg = (await outRxQ.next()).value;
110
- expect(mapTellKnownStateMsg).toEqual({
111
- action: "known",
112
- ...map.core.knownState(),
113
- });
114
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
115
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
116
- const mapNewContentMsg = (await outRxQ.next()).value;
117
- expect(mapNewContentMsg).toEqual({
118
- action: "content",
119
- id: map.core.id,
120
- header: undefined,
121
- new: {
122
- [node.currentSessionID]: {
123
- after: 1,
124
- newTransactions: [
125
- {
126
- privacy: "trusting",
127
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
128
- .transactions[1].madeAt,
129
- changes: stableStringify([
130
- {
131
- op: "set",
132
- key: "goodbye",
133
- value: "world",
134
- },
135
- ]),
136
- },
137
- ],
138
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
139
- .lastSignature,
140
- },
141
- },
142
- priority: getPriorityFromHeader(map.core.header),
143
- });
144
- });
145
- test.todo("TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues");
146
- test("After subscribing, node sends own known state and new txs to peer", async () => {
147
- const [admin, session] = randomAnonymousAccountAndSessionID();
148
- const node = new LocalNode(admin, session, Crypto);
149
- const group = node.createGroup();
150
- const map = group.createMap();
151
- const [inRx, inTx] = newQueuePair();
152
- const [outRx, outTx] = newQueuePair();
153
- const outRxQ = outRx[Symbol.asyncIterator]();
154
- node.syncManager.addPeer({
155
- id: "test",
156
- incoming: inRx,
157
- outgoing: outTx,
158
- role: "peer",
159
- crashOnClose: true,
160
- });
161
- await inTx.push({
162
- action: "load",
163
- id: map.core.id,
164
- header: false,
165
- sessions: {
166
- [node.currentSessionID]: 0,
167
- },
168
- });
169
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
170
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
171
- const mapTellKnownStateMsg = (await outRxQ.next()).value;
172
- expect(mapTellKnownStateMsg).toEqual({
173
- action: "known",
174
- ...map.core.knownState(),
175
- });
176
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
177
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
178
- const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
179
- expect(mapNewContentHeaderOnlyMsg).toEqual({
180
- action: "content",
181
- id: map.core.id,
182
- header: map.core.header,
183
- new: {},
184
- priority: getPriorityFromHeader(map.core.header),
185
- });
186
- map.set("hello", "world", "trusting");
187
- const mapEditMsg1 = (await outRxQ.next()).value;
188
- expect(mapEditMsg1).toEqual({
189
- action: "content",
190
- id: map.core.id,
191
- new: {
192
- [node.currentSessionID]: {
193
- after: 0,
194
- newTransactions: [
195
- {
196
- privacy: "trusting",
197
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
198
- .transactions[0].madeAt,
199
- changes: stableStringify([
200
- {
201
- op: "set",
202
- key: "hello",
203
- value: "world",
204
- },
205
- ]),
206
- },
207
- ],
208
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
209
- .lastSignature,
210
- },
211
- },
212
- priority: getPriorityFromHeader(map.core.header),
213
- });
214
- map.set("goodbye", "world", "trusting");
215
- const mapEditMsg2 = (await outRxQ.next()).value;
216
- expect(mapEditMsg2).toEqual({
217
- action: "content",
218
- id: map.core.id,
219
- new: {
220
- [node.currentSessionID]: {
221
- after: 1,
222
- newTransactions: [
223
- {
224
- privacy: "trusting",
225
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
226
- .transactions[1].madeAt,
227
- changes: stableStringify([
228
- {
229
- op: "set",
230
- key: "goodbye",
231
- value: "world",
232
- },
233
- ]),
234
- },
235
- ],
236
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
237
- .lastSignature,
238
- },
239
- },
240
- priority: getPriorityFromHeader(map.core.header),
241
- });
9
+ let jazzCloud = setupTestNode({
10
+ isSyncServer: true,
242
11
  });
243
- test("Client replies with known new content to tellKnownState from server", async () => {
244
- const [admin, session] = randomAnonymousAccountAndSessionID();
245
- const node = new LocalNode(admin, session, Crypto);
246
- const group = node.createGroup();
247
- const map = group.createMap();
248
- map.set("hello", "world", "trusting");
249
- const [inRx, inTx] = newQueuePair();
250
- const [outRx, outTx] = newQueuePair();
251
- const outRxQ = outRx[Symbol.asyncIterator]();
252
- node.syncManager.addPeer({
253
- id: "test",
254
- incoming: inRx,
255
- outgoing: outTx,
256
- role: "peer",
257
- crashOnClose: true,
258
- });
259
- // expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
260
- await inTx.push({
261
- action: "known",
262
- id: map.core.id,
263
- header: false,
264
- sessions: {
265
- [node.currentSessionID]: 0,
266
- },
267
- });
268
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
269
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
270
- const mapTellKnownStateMsg = (await outRxQ.next()).value;
271
- expect(mapTellKnownStateMsg).toEqual({
272
- action: "known",
273
- ...map.core.knownState(),
274
- });
275
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
276
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
277
- const mapNewContentMsg = (await outRxQ.next()).value;
278
- expect(mapNewContentMsg).toEqual({
279
- action: "content",
280
- id: map.core.id,
281
- header: map.core.header,
282
- new: {
283
- [node.currentSessionID]: {
284
- after: 0,
285
- newTransactions: [
286
- {
287
- privacy: "trusting",
288
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
289
- .transactions[0].madeAt,
290
- changes: stableStringify([
291
- {
292
- op: "set",
293
- key: "hello",
294
- value: "world",
295
- },
296
- ]),
297
- },
298
- ],
299
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
300
- .lastSignature,
301
- },
302
- },
303
- priority: getPriorityFromHeader(map.core.header),
304
- });
305
- });
306
- test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
307
- const [admin, session] = randomAnonymousAccountAndSessionID();
308
- const node = new LocalNode(admin, session, Crypto);
309
- const group = node.createGroup();
310
- const map = group.createMap();
311
- const [inRx, inTx] = newQueuePair();
312
- const [outRx, outTx] = newQueuePair();
313
- const outRxQ = outRx[Symbol.asyncIterator]();
314
- node.syncManager.addPeer({
315
- id: "test",
316
- incoming: inRx,
317
- outgoing: outTx,
318
- role: "peer",
319
- crashOnClose: true,
320
- });
321
- await inTx.push({
322
- action: "load",
323
- id: map.core.id,
324
- header: false,
325
- sessions: {
326
- [node.currentSessionID]: 0,
327
- },
328
- });
329
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
330
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
331
- const mapTellKnownStateMsg = (await outRxQ.next()).value;
332
- expect(mapTellKnownStateMsg).toEqual({
333
- action: "known",
334
- ...map.core.knownState(),
335
- });
336
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
337
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
338
- const mapNewContentHeaderOnlyMsg = (await outRxQ.next()).value;
339
- expect(mapNewContentHeaderOnlyMsg).toEqual({
340
- action: "content",
341
- id: map.core.id,
342
- header: map.core.header,
343
- new: {},
344
- priority: getPriorityFromHeader(map.core.header),
345
- });
346
- map.set("hello", "world", "trusting");
347
- map.set("goodbye", "world", "trusting");
348
- const _mapEditMsgs = (await outRxQ.next()).value;
349
- console.log("Sending correction");
350
- await inTx.push({
351
- action: "known",
352
- isCorrection: true,
353
- id: map.core.id,
354
- header: true,
355
- sessions: {
356
- [node.currentSessionID]: 1,
357
- },
358
- });
359
- const newContentAfterWrongAssumedState = (await outRxQ.next()).value;
360
- expect(newContentAfterWrongAssumedState).toEqual({
361
- action: "content",
362
- id: map.core.id,
363
- header: undefined,
364
- new: {
365
- [node.currentSessionID]: {
366
- after: 1,
367
- newTransactions: [
368
- {
369
- privacy: "trusting",
370
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
371
- .transactions[1].madeAt,
372
- changes: stableStringify([
373
- {
374
- op: "set",
375
- key: "goodbye",
376
- value: "world",
377
- },
378
- ]),
379
- },
380
- ],
381
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
382
- .lastSignature,
383
- },
384
- },
385
- priority: getPriorityFromHeader(map.core.header),
12
+ beforeEach(async () => {
13
+ jazzCloud = setupTestNode({
14
+ isSyncServer: true,
386
15
  });
387
16
  });
388
17
  test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
@@ -408,376 +37,59 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m
408
37
  ]);
409
38
  expect(result).toEqual("neverHappened");
410
39
  });
411
- test.todo("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", async () => {
412
- const [admin, session] = randomAnonymousAccountAndSessionID();
413
- const node = new LocalNode(admin, session, Crypto);
414
- const group = node.createGroup();
415
- const map = group.createMap();
416
- const [inRx, _inTx] = newQueuePair();
417
- const [outRx, outTx] = newQueuePair();
418
- const outRxQ = outRx[Symbol.asyncIterator]();
419
- node.syncManager.addPeer({
420
- id: "test",
421
- incoming: inRx,
422
- outgoing: outTx,
423
- role: "server",
424
- crashOnClose: true,
425
- });
426
- // expect((await outRxQ.next()).value).toMatchObject({
427
- // action: "load",
428
- // id: adminID,
429
- // });
430
- expect((await outRxQ.next()).value).toMatchObject({
431
- action: "load",
432
- id: group.core.id,
433
- });
434
- const mapSubscribeMsg = (await outRxQ.next()).value;
435
- expect(mapSubscribeMsg).toEqual({
436
- action: "load",
437
- id: map.core.id,
438
- header: true,
439
- sessions: {},
440
- });
441
- map.set("hello", "world", "trusting");
442
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(admin.id));
443
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
444
- const mapNewContentMsg = (await outRxQ.next()).value;
445
- expect(mapNewContentMsg).toEqual({
446
- action: "content",
447
- id: map.core.id,
448
- header: map.core.header,
449
- new: {
450
- [node.currentSessionID]: {
451
- after: 0,
452
- newTransactions: [
453
- {
454
- privacy: "trusting",
455
- madeAt: map.core.sessionLogs.get(node.currentSessionID)
456
- .transactions[0].madeAt,
457
- changes: stableStringify([
458
- {
459
- op: "set",
460
- key: "hello",
461
- value: "world",
462
- },
463
- ]),
464
- },
465
- ],
466
- lastSignature: map.core.sessionLogs.get(node.currentSessionID)
467
- .lastSignature,
468
- },
469
- },
470
- priority: getPriorityFromHeader(map.core.header),
471
- });
472
- });
473
- test.skip("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
474
- const [admin, session] = randomAnonymousAccountAndSessionID();
475
- const node = new LocalNode(admin, session, Crypto);
476
- const group = node.createGroup();
477
- const [inRx, _inTx] = newQueuePair();
478
- const [outRx, outTx] = newQueuePair();
479
- const outRxQ = outRx[Symbol.asyncIterator]();
480
- node.syncManager.addPeer({
481
- id: "test",
482
- incoming: inRx,
483
- outgoing: outTx,
484
- role: "server",
485
- crashOnClose: true,
486
- });
487
- // expect((await outRxQ.next()).value).toMatchObject({
488
- // action: "load",
489
- // id: admin.id,
490
- // });
491
- expect((await outRxQ.next()).value).toMatchObject({
492
- action: "load",
493
- id: group.core.id,
494
- });
495
- const map = group.createMap();
496
- const mapSubscribeMsg = (await outRxQ.next()).value;
497
- expect(mapSubscribeMsg).toEqual({
498
- action: "load",
499
- ...map.core.knownState(),
500
- });
501
- // expect((await outRxQ.next()).value).toMatchObject(admContEx(adminID));
502
- expect((await outRxQ.next()).value).toMatchObject(groupContentEx(group));
503
- const mapContentMsg = (await outRxQ.next()).value;
504
- expect(mapContentMsg).toEqual({
505
- action: "content",
506
- id: map.core.id,
507
- header: map.core.header,
508
- new: {},
509
- priority: getPriorityFromHeader(map.core.header),
510
- });
511
- });
512
- test.todo("TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it");
513
- test("When we connect a new server peer, we try to sync all existing coValues to it", async () => {
514
- const [admin, session] = randomAnonymousAccountAndSessionID();
515
- const node = new LocalNode(admin, session, Crypto);
516
- const group = node.createGroup();
517
- const map = group.createMap();
518
- const [inRx, _inTx] = newQueuePair();
519
- const [outRx, outTx] = newQueuePair();
520
- const outRxQ = outRx[Symbol.asyncIterator]();
521
- node.syncManager.addPeer({
522
- id: "test",
523
- incoming: inRx,
524
- outgoing: outTx,
525
- role: "server",
526
- crashOnClose: true,
527
- });
528
- // const _adminSubscribeMessage = await outRxQ.next();
529
- const groupSubscribeMessage = (await outRxQ.next()).value;
530
- expect(groupSubscribeMessage).toEqual({
531
- action: "load",
532
- ...group.core.knownState(),
533
- });
534
- const secondMessage = (await outRxQ.next()).value;
535
- expect(secondMessage).toEqual({
536
- action: "load",
537
- ...map.core.knownState(),
538
- });
539
- });
540
- 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 () => {
541
- const [admin, session] = randomAnonymousAccountAndSessionID();
542
- const node = new LocalNode(admin, session, Crypto);
543
- const group = node.createGroup();
544
- const map = group.createMap();
545
- const [inRx, inTx] = newQueuePair();
546
- const [outRx, outTx] = newQueuePair();
547
- const outRxQ = outRx[Symbol.asyncIterator]();
548
- node.syncManager.addPeer({
549
- id: "test",
550
- incoming: inRx,
551
- outgoing: outTx,
552
- role: "peer",
553
- crashOnClose: true,
554
- });
555
- await inTx.push({
556
- action: "load",
557
- id: map.core.id,
558
- header: true,
559
- sessions: {
560
- [node.currentSessionID]: 1,
561
- },
562
- });
563
- // expect((await outRxQ.next()).value).toMatchObject(admStateEx(admin.id));
564
- expect((await outRxQ.next()).value).toMatchObject(groupStateEx(group));
565
- const mapTellKnownState = (await outRxQ.next()).value;
566
- expect(mapTellKnownState).toEqual({
567
- action: "known",
568
- ...map.core.knownState(),
569
- });
570
- });
571
- test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => {
572
- // 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
573
- const [admin, session] = randomAnonymousAccountAndSessionID();
574
- const node1 = new LocalNode(admin, session, Crypto);
575
- const group = node1.createGroup();
576
- const [inRx1, inTx1] = newQueuePair();
577
- const [outRx1, outTx1] = newQueuePair();
578
- const outRxQ1 = outRx1[Symbol.asyncIterator]();
579
- node1.syncManager.addPeer({
580
- id: "test2",
581
- incoming: inRx1,
582
- outgoing: outTx1,
583
- role: "server",
584
- crashOnClose: true,
585
- });
586
- const node2 = new LocalNode(admin, Crypto.newRandomSessionID(admin.id), Crypto);
587
- const [inRx2, inTx2] = newQueuePair();
588
- const [outRx2, outTx2] = newQueuePair();
589
- const outRxQ2 = outRx2[Symbol.asyncIterator]();
590
- node2.syncManager.addPeer({
591
- id: "test1",
592
- incoming: inRx2,
593
- outgoing: outTx2,
594
- role: "client",
595
- crashOnClose: true,
596
- });
597
- const adminSubscribeMessage = (await outRxQ1.next()).value;
598
- expect(adminSubscribeMessage).toMatchObject({
599
- action: "load",
600
- id: admin.id,
601
- });
602
- const groupSubscribeMsg = (await outRxQ1.next()).value;
603
- expect(groupSubscribeMsg).toMatchObject({
604
- action: "load",
605
- id: group.core.id,
606
- });
607
- await inTx2.push(adminSubscribeMessage);
608
- await inTx2.push(groupSubscribeMsg);
609
- // const adminTellKnownStateMsg = (await outRxQ2.next()).value;
610
- // expect(adminTellKnownStateMsg).toMatchObject(admStateEx(admin.id));
611
- const groupTellKnownStateMsg = (await outRxQ2.next()).value;
612
- expect(groupTellKnownStateMsg).toMatchObject(groupStateEx(group));
613
- expect(node2.syncManager.peers["test1"].optimisticKnownStates.has(group.core.id)).toBeDefined();
614
- // await inTx1.push(adminTellKnownStateMsg);
615
- await inTx1.push(groupTellKnownStateMsg);
616
- // const adminContentMsg = (await outRxQ1.next()).value;
617
- // expect(adminContentMsg).toMatchObject(admContEx(admin.id));
618
- const groupContentMsg = (await outRxQ1.next()).value;
619
- expect(groupContentMsg).toMatchObject(groupContentEx(group));
620
- // await inTx2.push(adminContentMsg);
621
- await inTx2.push(groupContentMsg);
622
- const map = group.createMap();
623
- const mapSubscriptionMsg = (await outRxQ1.next()).value;
624
- expect(mapSubscriptionMsg).toMatchObject({
625
- action: "load",
626
- id: map.core.id,
627
- });
628
- const mapNewContentMsg = (await outRxQ1.next()).value;
629
- expect(mapNewContentMsg).toEqual({
630
- action: "content",
631
- id: map.core.id,
632
- header: map.core.header,
633
- new: {},
634
- priority: getPriorityFromHeader(map.core.header),
635
- });
636
- await inTx2.push(mapSubscriptionMsg);
637
- const mapTellKnownStateMsg = (await outRxQ2.next()).value;
638
- expect(mapTellKnownStateMsg).toEqual({
639
- action: "known",
640
- id: map.core.id,
641
- header: false,
642
- sessions: {},
643
- });
644
- expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("loading");
645
- await inTx2.push(mapNewContentMsg);
646
- map.set("hello", "world", "trusting");
647
- const mapEditMsg = (await outRxQ1.next()).value;
648
- await inTx2.push(mapEditMsg);
649
- await new Promise((resolve) => setTimeout(resolve, 100));
650
- expect(expectMap(node2.expectCoValueLoaded(map.core.id).getCurrentContent()).get("hello")).toEqual("world");
651
- });
652
- 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 () => {
653
- /*
654
- // 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
655
- const [admin, session] = randomAnonymousAccountAndSessionID();
656
-
657
- const node1 = new LocalNode(admin, session, Crypto);
658
-
659
- const group = node1.createGroup();
660
-
661
- const map = group.createMap();
662
- map.set("hello", "world", "trusting");
663
-
664
- const node2 = new LocalNode(admin, newRandomSessionID(admin.id), Crypto);
665
-
666
- const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2");
667
-
668
- node1.syncManager.addPeer(node2asPeer);
669
- node2.syncManager.addPeer(node1asPeer);
670
-
671
- await node2.loadCoValueCore(map.core.id);
672
-
673
- expect(
674
- expectMap(
675
- node2.expectCoValueLoaded(map.core.id).getCurrentContent(),
676
- ).get("hello"),
677
- ).toEqual("world");
678
- */
679
- });
680
40
  test("Can sync a coValue through a server to another client", async () => {
681
- const { node: client1 } = await createConnectedTestNode();
41
+ const { node: client1 } = await setupTestAccount({
42
+ connected: true,
43
+ });
682
44
  const group = client1.createGroup();
683
45
  const map = group.createMap();
684
46
  map.set("hello", "world", "trusting");
685
- const { node: client2 } = await createConnectedTestNode();
47
+ const { node: client2 } = await setupTestAccount({
48
+ connected: true,
49
+ });
686
50
  const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
687
51
  expect(mapOnClient2.get("hello")).toEqual("world");
688
52
  });
689
53
  test("Can sync a coValue with private transactions through a server to another client", async () => {
690
- const { node: client1 } = await createConnectedTestNode();
54
+ const { node: client1 } = await setupTestAccount({
55
+ connected: true,
56
+ });
691
57
  const group = client1.createGroup();
692
58
  const map = group.createMap();
693
59
  map.set("hello", "world", "private");
694
60
  group.addMember("everyone", "reader");
695
- const { node: client2 } = await createConnectedTestNode();
61
+ const { node: client2 } = await setupTestAccount({
62
+ connected: true,
63
+ });
696
64
  const mapOnClient2 = await loadCoValueOrFail(client2, map.id);
697
65
  expect(mapOnClient2.get("hello")).toEqual("world");
698
66
  });
699
- test.skip("When a peer's incoming/readable stream closes, we remove the peer", async () => {
700
- /*
701
- const [admin, session] = randomAnonymousAccountAndSessionID();
702
- const node = new LocalNode(admin, session, Crypto);
703
-
704
- const group = node.createGroup();
705
-
706
- const [inRx, inTx] = await Effect.runPromise(newStreamPair());
707
- const [outRx, outTx] = await Effect.runPromise(newStreamPair());
708
-
709
- node.syncManager.addPeer({
710
- id: "test",
711
- incoming: inRx,
712
- outgoing: outTx,
713
- role: "server",
714
- });
715
-
716
- // expect(yield* Queue.take(outRxQ)).toMatchObject({
717
- // action: "load",
718
- // id: admin.id,
719
- // });
720
- expect(yield * Queue.take(outRxQ)).toMatchObject({
721
- action: "load",
722
- id: group.core.id,
723
- });
724
-
725
- const map = group.createMap();
726
-
727
- const mapSubscribeMsg = await reader.read();
728
-
729
- expect(mapSubscribeMsg.value).toEqual({
730
- action: "load",
731
- ...map.core.knownState(),
732
- } satisfies SyncMessage);
733
-
734
- // expect(yield* Queue.take(outRxQ)).toMatchObject(admContEx(admin.id));
735
- expect(yield * Queue.take(outRxQ)).toMatchObject(groupContentEx(group));
736
-
737
- const mapContentMsg = await reader.read();
738
-
739
- expect(mapContentMsg.value).toEqual({
740
- action: "content",
741
- id: map.core.id,
742
- header: map.core.header,
743
- new: {},
744
- } satisfies SyncMessage);
745
-
746
- await inTx.abort();
747
-
748
- await new Promise((resolve) => setTimeout(resolve, 100));
749
-
750
- expect(node.syncManager.peers["test"]).toBeUndefined();
751
- */
752
- });
753
67
  test("should keep the peer state when the peer closes", async () => {
754
- const client = createTestNode();
755
- const { nodeToServerPeer, serverToNodePeer } = connectNodeToSyncServer(client);
756
- const group = jazzCloud.createGroup();
68
+ const client = setupTestNode();
69
+ const { peer, peerState } = client.connectToSyncServer();
70
+ const group = jazzCloud.node.createGroup();
757
71
  const map = group.createMap();
758
72
  map.set("hello", "world", "trusting");
759
- await client.loadCoValueCore(map.core.id);
760
- const syncManager = client.syncManager;
761
- const peerState = syncManager.peers[nodeToServerPeer.id];
73
+ await client.node.loadCoValueCore(map.core.id);
74
+ const syncManager = client.node.syncManager;
762
75
  // @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
763
- await serverToNodePeer.outgoing.push("Disconnected");
76
+ await peer.outgoing.push("Disconnected");
764
77
  await waitFor(() => peerState?.closed);
765
- expect(syncManager.peers[nodeToServerPeer.id]).not.toBeUndefined();
78
+ expect(syncManager.peers[peer.id]).not.toBeUndefined();
766
79
  });
767
80
  test("should delete the peer state when the peer closes if deletePeerStateOnClose is true", async () => {
768
- const client = createTestNode();
769
- const { nodeToServerPeer, serverToNodePeer } = connectNodeToSyncServer(client);
770
- nodeToServerPeer.deletePeerStateOnClose = true;
771
- const group = jazzCloud.createGroup();
81
+ const client = setupTestNode();
82
+ const { peer, peerState } = client.connectToSyncServer();
83
+ peer.deletePeerStateOnClose = true;
84
+ const group = jazzCloud.node.createGroup();
772
85
  const map = group.createMap();
773
86
  map.set("hello", "world", "trusting");
774
- await client.loadCoValueCore(map.core.id);
775
- const syncManager = client.syncManager;
776
- const peerState = syncManager.peers[nodeToServerPeer.id];
87
+ await client.node.loadCoValueCore(map.core.id);
88
+ const syncManager = client.node.syncManager;
777
89
  // @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
778
- await serverToNodePeer.outgoing.push("Disconnected");
90
+ await peer.outgoing.push("Disconnected");
779
91
  await waitFor(() => peerState?.closed);
780
- expect(syncManager.peers[nodeToServerPeer.id]).toBeUndefined();
92
+ expect(syncManager.peers[peer.id]).toBeUndefined();
781
93
  });
782
94
  describe("sync - extra tests", () => {
783
95
  test("Node handles disconnection and reconnection of a peer gracefully", async () => {
@@ -915,58 +227,6 @@ describe("sync - extra tests", () => {
915
227
  expect(finalStateNode2.toJSON()).toEqual(expectedState);
916
228
  expect(finalStateNode3.toJSON()).toEqual(expectedState);
917
229
  });
918
- test.skip("Large coValues are synced efficiently in chunks", async () => {
919
- // Create two nodes
920
- const [admin1, session1] = randomAnonymousAccountAndSessionID();
921
- const node1 = new LocalNode(admin1, session1, Crypto);
922
- const [admin2, session2] = randomAnonymousAccountAndSessionID();
923
- const node2 = new LocalNode(admin2, session2, Crypto);
924
- // Create a group and a large map on node1
925
- const group = node1.createGroup();
926
- group.addMember("everyone", "writer");
927
- const largeMap = group.createMap();
928
- // Generate a large amount of data (about 10MB)
929
- const dataSize = 1 * 1024 * 1024;
930
- const chunkSize = 1024; // 1KB chunks
931
- const chunks = dataSize / chunkSize;
932
- for (let i = 0; i < chunks; i++) {
933
- const key = `key${i}`;
934
- const value = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
935
- largeMap.set(key, value, "trusting");
936
- }
937
- // Connect the nodes
938
- const [node1AsPeer, node2AsPeer] = connectedPeers("node1", "node2", {
939
- peer1role: "server",
940
- peer2role: "client",
941
- });
942
- node1.syncManager.addPeer(node2AsPeer);
943
- node2.syncManager.addPeer(node1AsPeer);
944
- await new Promise((resolve) => setTimeout(resolve, 4000));
945
- // Measure sync time
946
- const startSync = performance.now();
947
- // Load the large map on node2
948
- const largeMapOnNode2 = await node2.loadCoValueCore(largeMap.core.id);
949
- if (largeMapOnNode2 === "unavailable") {
950
- throw new Error("Large map is unavailable on node2");
951
- }
952
- const endSync = performance.now();
953
- const syncTime = endSync - startSync;
954
- // Verify that all data was synced correctly
955
- const syncedMap = new RawCoMap(largeMapOnNode2);
956
- expect(Object.keys(largeMapOnNode2.getCurrentContent().toJSON() || {}).length).toBe(chunks);
957
- for (let i = 0; i < chunks; i++) {
958
- const key = `key${i}`;
959
- const expectedValue = Buffer.alloc(chunkSize, `value${i}`).toString("base64");
960
- expect(syncedMap.get(key)).toBe(expectedValue);
961
- }
962
- // Check that sync time is reasonable (this threshold may need adjustment)
963
- const reasonableSyncTime = 10; // 30 seconds
964
- expect(syncTime).toBeLessThan(reasonableSyncTime);
965
- // Check memory usage (this threshold may need adjustment)
966
- const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
967
- const reasonableMemoryUsage = 1; // 500 MB
968
- expect(memoryUsage).toBeLessThan(reasonableMemoryUsage);
969
- });
970
230
  test("Node correctly handles and recovers from network partitions", async () => {
971
231
  // Create three nodes
972
232
  const [admin1, session1] = randomAnonymousAccountAndSessionID();
@@ -1094,7 +354,9 @@ test("a value created on one node can be loaded on anotehr node even if not dire
1094
354
  });
1095
355
  describe("SyncManager - knownStates vs optimisticKnownStates", () => {
1096
356
  test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
1097
- const { node: client } = await createConnectedTestNode();
357
+ const { node: client } = await setupTestAccount({
358
+ connected: true,
359
+ });
1098
360
  // Create test data
1099
361
  const group = client.createGroup();
1100
362
  const mapOnClient = group.createMap();
@@ -1103,31 +365,31 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
1103
365
  // Wait for the full sync to complete
1104
366
  await mapOnClient.core.waitForSync();
1105
367
  const peerStateClient = client.syncManager.getPeers()[0];
1106
- const peerStateJazzCloud = jazzCloud.syncManager.getPeers()[0];
368
+ const peerStateJazzCloud = jazzCloud.node.syncManager.getPeers()[0];
1107
369
  // The optimisticKnownStates should be the same as the knownStates after the full sync is complete
1108
370
  expect(peerStateClient.optimisticKnownStates.get(mapOnClient.core.id)).toEqual(peerStateClient.knownStates.get(mapOnClient.core.id));
1109
371
  // On the other node the knownStates should be updated correctly based on the messages we received
1110
372
  expect(peerStateJazzCloud.optimisticKnownStates.get(mapOnClient.core.id)).toEqual(peerStateJazzCloud.knownStates.get(mapOnClient.core.id));
1111
373
  });
1112
374
  test("optimisticKnownStates is updated as new transactions are sent, while knownStates only when the updates are acknowledged", async () => {
1113
- const { node: client, nodeToServerPeer } = await createConnectedTestNode();
375
+ const client = await setupTestAccount();
376
+ const { peer, peerState } = client.connectToSyncServer();
1114
377
  // Create test data and sync the first change
1115
378
  // We want that both the nodes know about the coValue so we can test
1116
379
  // the content acknowledgement flow.
1117
- const group = client.createGroup();
380
+ const group = client.node.createGroup();
1118
381
  const map = group.createMap();
1119
382
  map.set("key1", "value1", "trusting");
1120
- await client.syncManager.actuallySyncCoValue(map.core);
383
+ await client.node.syncManager.actuallySyncCoValue(map.core);
1121
384
  await map.core.waitForSync();
1122
385
  // Block the content messages
1123
386
  // The main difference between optimisticKnownStates and knownStates is that
1124
387
  // optimisticKnownStates is updated when the content messages are sent,
1125
388
  // while knownStates is only updated when we receive the "known" messages
1126
389
  // that are acknowledging the receipt of the content messages
1127
- const outgoing = blockMessageTypeOnOutgoingPeer(nodeToServerPeer, "content");
390
+ const outgoing = blockMessageTypeOnOutgoingPeer(peer, "content");
1128
391
  map.set("key2", "value2", "trusting");
1129
- await client.syncManager.actuallySyncCoValue(map.core);
1130
- const peerState = client.syncManager.peers[nodeToServerPeer.id];
392
+ await client.node.syncManager.actuallySyncCoValue(map.core);
1131
393
  expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(peerState.knownStates.get(map.core.id));
1132
394
  // Restore the implementation of push and send the blocked messages
1133
395
  // After this the full sync can be completed and the other node will
@@ -1140,37 +402,35 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
1140
402
  });
1141
403
  describe("SyncManager.addPeer", () => {
1142
404
  test("new peer gets a copy of previous peer's knownStates when replacing it", async () => {
1143
- const { node: client } = await createConnectedTestNode();
405
+ const client = await setupTestAccount();
406
+ const { peerState: firstPeerState, getCurrentPeerState } = client.connectToSyncServer();
1144
407
  // Create test data
1145
- const group = client.createGroup();
408
+ const group = client.node.createGroup();
1146
409
  const map = group.createMap();
1147
410
  map.set("key1", "value1", "trusting");
1148
- await client.syncManager.actuallySyncCoValue(map.core);
411
+ await client.node.syncManager.actuallySyncCoValue(map.core);
1149
412
  // Wait for initial sync
1150
413
  await map.core.waitForSync();
1151
- const firstPeerState = client.syncManager.getPeers()[0];
1152
414
  // Store the initial known states
1153
415
  const initialKnownStates = firstPeerState.knownStates;
1154
416
  // Create new connection with same ID
1155
- const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
1156
- peer1role: "server",
1157
- peer2role: "client",
1158
- });
1159
- // Add new peer with same ID
1160
- client.syncManager.addPeer(secondPeer);
1161
- const newPeerState = client.syncManager.getPeers()[0];
417
+ client.connectToSyncServer();
418
+ // Wait for the new peer to be added
419
+ await waitFor(() => expect(getCurrentPeerState()).not.toBe(firstPeerState));
1162
420
  // Verify that the new peer has a copy of the previous known states
1163
- const newPeerKnownStates = newPeerState.knownStates;
421
+ const newPeerKnownStates = getCurrentPeerState().knownStates;
1164
422
  expect(newPeerKnownStates).not.toBe(initialKnownStates); // Should be a different instance
1165
423
  expect(newPeerKnownStates.get(map.core.id)).toEqual(initialKnownStates.get(map.core.id));
1166
424
  });
1167
425
  test("new peer with new ID starts with empty knownStates", async () => {
1168
- const { node: client } = await createConnectedTestNode();
426
+ const client = await setupTestAccount({
427
+ connected: true,
428
+ });
1169
429
  // Create test data
1170
- const group = client.createGroup();
430
+ const group = client.node.createGroup();
1171
431
  const map = group.createMap();
1172
432
  map.set("key1", "value1", "trusting");
1173
- await client.syncManager.actuallySyncCoValue(map.core);
433
+ await client.node.syncManager.actuallySyncCoValue(map.core);
1174
434
  // Wait for initial sync
1175
435
  await map.core.waitForSync();
1176
436
  // Connect second peer with different ID
@@ -1179,72 +439,82 @@ describe("SyncManager.addPeer", () => {
1179
439
  peer2role: "server",
1180
440
  });
1181
441
  // Add new peer with different ID
1182
- client.syncManager.addPeer(brandNewPeer);
442
+ client.node.syncManager.addPeer(brandNewPeer);
1183
443
  // Verify that the new peer starts with empty known states
1184
- const newPeerKnownStates = client.syncManager.peers["brandNewPeer"].knownStates;
444
+ const newPeerKnownStates = client.node.syncManager.peers["brandNewPeer"].knownStates;
1185
445
  expect(newPeerKnownStates.get(map.core.id)).toBe(undefined);
1186
446
  });
1187
447
  test("when adding a peer with the same ID as a previous peer, the previous peer is closed", async () => {
1188
- const { node: client } = await createConnectedTestNode();
448
+ const client = await setupTestAccount({});
449
+ const { peerState: firstPeerState } = client.connectToSyncServer();
1189
450
  // Store reference to first peer
1190
- const firstPeer = client.syncManager.getPeers()[0];
1191
- const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
451
+ const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
1192
452
  // Create and add replacement peer
1193
- const [secondPeer] = connectedPeers(firstPeer.id, "unusedPeer", {
453
+ const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
1194
454
  peer1role: "server",
1195
455
  peer2role: "client",
1196
456
  });
1197
- client.syncManager.addPeer(secondPeer);
457
+ client.node.syncManager.addPeer(secondPeer);
1198
458
  // Verify thet the first peer had ben closed correctly
1199
459
  expect(closeSpy).toHaveBeenCalled();
1200
- expect(firstPeer.closed).toBe(true);
460
+ expect(firstPeerState.closed).toBe(true);
1201
461
  });
1202
462
  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 () => {
1203
- const { node: client } = await createConnectedTestNode();
463
+ const client = await setupTestAccount({
464
+ connected: true,
465
+ });
1204
466
  // Store reference to first peer
1205
- const firstPeer = client.syncManager.getPeers()[0];
1206
- firstPeer.gracefulShutdown();
1207
- const closeSpy = vi.spyOn(firstPeer, "gracefulShutdown");
467
+ const { peerState: firstPeerState } = client.connectToSyncServer();
468
+ firstPeerState.gracefulShutdown();
469
+ const closeSpy = vi.spyOn(firstPeerState, "gracefulShutdown");
1208
470
  // Create and add replacement peer
1209
- const [secondPeer] = connectedPeers(firstPeer.id, "unusedPeer", {
471
+ const [secondPeer] = connectedPeers(firstPeerState.id, "unusedPeer", {
1210
472
  peer1role: "server",
1211
473
  peer2role: "client",
1212
474
  });
1213
- client.syncManager.addPeer(secondPeer);
475
+ client.node.syncManager.addPeer(secondPeer);
1214
476
  // Verify thet the first peer had not been closed again
1215
477
  expect(closeSpy).not.toHaveBeenCalled();
1216
- expect(firstPeer.closed).toBe(true);
478
+ expect(firstPeerState.closed).toBe(true);
1217
479
  });
1218
480
  test("when adding a server peer the local coValues should be sent to it", async () => {
1219
- const { node: client, addServerPeer } = await createConnectedTestNode({
481
+ const client = await setupTestAccount({
1220
482
  connected: false,
1221
483
  });
1222
- const group = client.createGroup();
484
+ const group = client.node.createGroup();
1223
485
  const map = group.createMap();
1224
486
  map.set("key1", "value1", "trusting");
1225
- addServerPeer();
487
+ client.connectToSyncServer();
1226
488
  await map.core.waitForSync();
1227
- expect(jazzCloud.coValuesStore.get(map.id).state.type).toBe("available");
489
+ expect(jazzCloud.node.coValuesStore.get(map.id).state.type).toBe("available");
1228
490
  });
1229
491
  });
1230
492
  describe("loadCoValueCore with retry", () => {
1231
493
  test("should load the value if available on the server", async () => {
1232
- const { node: client } = await createConnectedTestNode();
1233
- const { node: anotherClient } = await createConnectedTestNode();
1234
- const group = anotherClient.createGroup();
494
+ const client = await setupTestAccount({
495
+ connected: true,
496
+ });
497
+ const anotherClient = await setupTestAccount({
498
+ connected: true,
499
+ });
500
+ const group = anotherClient.node.createGroup();
1235
501
  const map = group.createMap();
1236
502
  map.set("key1", "value1", "trusting");
1237
- const promise = client.loadCoValueCore(map.id);
503
+ const promise = client.node.loadCoValueCore(map.id);
1238
504
  await expect(promise).resolves.not.toBe("unavailable");
1239
505
  });
1240
506
  test("should handle correctly two subsequent loads", async () => {
1241
- const { node: client } = await createConnectedTestNode();
1242
- const { node: anotherClient } = await createConnectedTestNode();
1243
- const group = anotherClient.createGroup();
507
+ const client = await setupTestAccount({
508
+ connected: true,
509
+ });
510
+ const anotherClient = await setupTestAccount({
511
+ connected: true,
512
+ });
513
+ const group = anotherClient.node.createGroup();
1244
514
  const map = group.createMap();
1245
515
  map.set("key1", "value1", "trusting");
1246
- const promise1 = client.loadCoValueCore(map.id);
1247
- const promise2 = client.loadCoValueCore(map.id);
516
+ const promise1 = client.node.loadCoValueCore(map.id);
517
+ const promise2 = client.node.loadCoValueCore(map.id);
1248
518
  await expect(promise1).resolves.not.toBe("unavailable");
1249
519
  await expect(promise2).resolves.not.toBe("unavailable");
1250
520
  });
@@ -1278,46 +548,44 @@ describe("loadCoValueCore with retry", () => {
1278
548
  });
1279
549
  describe("waitForSyncWithPeer", () => {
1280
550
  test("should resolve when the coValue is fully uploaded into the peer", async () => {
1281
- const { node: client } = await createConnectedTestNode();
551
+ const client = await setupTestAccount();
552
+ const { peerState } = client.connectToSyncServer();
1282
553
  // Create test data
1283
- const group = client.createGroup();
554
+ const group = client.node.createGroup();
1284
555
  const map = group.createMap();
1285
556
  map.set("key1", "value1", "trusting");
1286
- await client.syncManager.actuallySyncCoValue(map.core);
1287
- const peer = client.syncManager.getPeers()[0];
1288
- if (!peer) {
1289
- throw new Error("No peer found");
1290
- }
1291
- await expect(client.syncManager.waitForSyncWithPeer(peer.id, map.core.id, 100)).resolves.toBe(true);
557
+ await client.node.syncManager.actuallySyncCoValue(map.core);
558
+ await expect(client.node.syncManager.waitForSyncWithPeer(peerState.id, map.core.id, 100)).resolves.toBe(true);
1292
559
  });
1293
560
  test("should not resolve when the coValue is not synced", async () => {
1294
- const { node: client } = await createConnectedTestNode();
1295
- const peer = client.syncManager.getPeers()[0];
1296
- if (!peer) {
1297
- throw new Error("No peer found");
1298
- }
561
+ const client = await setupTestAccount();
562
+ const { peerState } = client.connectToSyncServer();
1299
563
  // Create test data
1300
- const group = client.createGroup();
564
+ const group = client.node.createGroup();
1301
565
  const map = group.createMap();
1302
566
  map.set("key1", "value1", "trusting");
1303
- vi.spyOn(peer, "pushOutgoingMessage").mockImplementation(async () => {
567
+ vi.spyOn(peerState, "pushOutgoingMessage").mockImplementation(async () => {
1304
568
  return Promise.resolve();
1305
569
  });
1306
- await client.syncManager.actuallySyncCoValue(map.core);
1307
- await expect(client.syncManager.waitForSyncWithPeer(peer.id, map.core.id, 100)).rejects.toThrow("Timeout");
570
+ await client.node.syncManager.actuallySyncCoValue(map.core);
571
+ await expect(client.node.syncManager.waitForSyncWithPeer(peerState.id, map.core.id, 100)).rejects.toThrow("Timeout");
1308
572
  });
1309
573
  });
1310
574
  test("Should not crash when syncing an unknown coValue type", async () => {
1311
- const { node: client } = await createConnectedTestNode();
1312
- const coValue = client.createCoValue({
575
+ const client = await setupTestAccount({
576
+ connected: true,
577
+ });
578
+ const coValue = client.node.createCoValue({
1313
579
  type: "ooops",
1314
580
  ruleset: { type: "unsafeAllowAll" },
1315
581
  meta: null,
1316
582
  ...Crypto.createdNowUnique(),
1317
583
  });
1318
584
  await coValue.waitForSync();
1319
- const { node: anotherClient } = await createConnectedTestNode();
1320
- const coValueOnTheOtherNode = await loadCoValueOrFail(anotherClient, coValue.getCurrentContent().id);
585
+ const anotherClient = await setupTestAccount({
586
+ connected: true,
587
+ });
588
+ const coValueOnTheOtherNode = await loadCoValueOrFail(anotherClient.node, coValue.getCurrentContent().id);
1321
589
  expect(coValueOnTheOtherNode.id).toBe(coValue.id);
1322
590
  });
1323
591
  describe("metrics", () => {
@@ -1395,183 +663,6 @@ describe("metrics", () => {
1395
663
  expect(connectedServerPeers).toBe(0);
1396
664
  });
1397
665
  });
1398
- describe("sync protocol", () => {
1399
- test("should have the correct messages exchanged between client and server", async () => {
1400
- // Creating the account from agent to simplify the messages exchange
1401
- const { node: client, messages } = await createConnectedTestAgentNode();
1402
- const group = client.createGroup();
1403
- const map = group.createMap();
1404
- map.set("hello", "world", "trusting");
1405
- await map.core.waitForSync();
1406
- const mapOnJazzCloud = await loadCoValueOrFail(jazzCloud, map.id);
1407
- expect(mapOnJazzCloud.get("hello")).toEqual("world");
1408
- expect(messages).toEqual([
1409
- {
1410
- from: "client",
1411
- msg: {
1412
- action: "load",
1413
- header: true,
1414
- id: group.id,
1415
- sessions: {
1416
- [client.currentSessionID]: 3,
1417
- },
1418
- },
1419
- },
1420
- {
1421
- from: "server",
1422
- msg: {
1423
- action: "load",
1424
- header: false,
1425
- id: group.id,
1426
- sessions: {},
1427
- },
1428
- },
1429
- {
1430
- from: "client",
1431
- msg: {
1432
- action: "load",
1433
- header: true,
1434
- id: map.id,
1435
- sessions: {
1436
- [client.currentSessionID]: 1,
1437
- },
1438
- },
1439
- },
1440
- {
1441
- from: "server",
1442
- msg: {
1443
- action: "load",
1444
- header: false,
1445
- id: map.id,
1446
- sessions: {},
1447
- },
1448
- },
1449
- {
1450
- from: "client",
1451
- msg: {
1452
- action: "content",
1453
- header: {
1454
- createdAt: expect.any(String),
1455
- meta: null,
1456
- ruleset: {
1457
- initialAdmin: client.account.id,
1458
- type: "group",
1459
- },
1460
- type: "comap",
1461
- uniqueness: expect.any(String),
1462
- },
1463
- id: group.id,
1464
- new: {
1465
- [client.currentSessionID]: {
1466
- after: 0,
1467
- lastSignature: expect.any(String),
1468
- newTransactions: expect.any(Array),
1469
- },
1470
- },
1471
- priority: 0,
1472
- },
1473
- },
1474
- {
1475
- from: "client",
1476
- msg: {
1477
- action: "content",
1478
- header: {
1479
- createdAt: expect.any(String),
1480
- meta: null,
1481
- ruleset: {
1482
- group: group.id,
1483
- type: "ownedByGroup",
1484
- },
1485
- type: "comap",
1486
- uniqueness: expect.any(String),
1487
- },
1488
- id: map.id,
1489
- new: {
1490
- [client.currentSessionID]: {
1491
- after: 0,
1492
- lastSignature: expect.any(String),
1493
- newTransactions: expect.any(Array),
1494
- },
1495
- },
1496
- priority: 3,
1497
- },
1498
- },
1499
- {
1500
- from: "server",
1501
- msg: {
1502
- action: "known",
1503
- header: true,
1504
- id: group.id,
1505
- sessions: {
1506
- [client.currentSessionID]: 3,
1507
- },
1508
- },
1509
- },
1510
- {
1511
- // TODO: This is a redundant message, we should remove it
1512
- from: "client",
1513
- msg: {
1514
- action: "content",
1515
- header: {
1516
- createdAt: expect.any(String),
1517
- meta: null,
1518
- ruleset: {
1519
- group: group.id,
1520
- type: "ownedByGroup",
1521
- },
1522
- type: "comap",
1523
- uniqueness: expect.any(String),
1524
- },
1525
- id: map.id,
1526
- new: {
1527
- [client.currentSessionID]: {
1528
- after: 0,
1529
- lastSignature: expect.any(String),
1530
- newTransactions: expect.any(Array),
1531
- },
1532
- },
1533
- priority: 3,
1534
- },
1535
- },
1536
- {
1537
- // TODO: This is a redundant message, we should remove it
1538
- from: "server",
1539
- msg: {
1540
- action: "known",
1541
- asDependencyOf: undefined,
1542
- header: true,
1543
- id: group.id,
1544
- sessions: {
1545
- [client.currentSessionID]: 3,
1546
- },
1547
- },
1548
- },
1549
- {
1550
- from: "server",
1551
- msg: {
1552
- action: "known",
1553
- header: true,
1554
- id: map.id,
1555
- sessions: {
1556
- [client.currentSessionID]: 1,
1557
- },
1558
- },
1559
- },
1560
- {
1561
- from: "server",
1562
- msg: {
1563
- action: "known",
1564
- asDependencyOf: undefined,
1565
- header: true,
1566
- id: map.id,
1567
- sessions: {
1568
- [client.currentSessionID]: 1,
1569
- },
1570
- },
1571
- },
1572
- ]);
1573
- });
1574
- });
1575
666
  function groupContentEx(group) {
1576
667
  return {
1577
668
  action: "content",
@@ -1586,20 +677,20 @@ function groupStateEx(group) {
1586
677
  }
1587
678
  describe("LocalNode.load", () => {
1588
679
  test("should throw error when trying to load with undefined ID", async () => {
1589
- const { node } = await createConnectedTestNode();
680
+ const client = await setupTestAccount();
1590
681
  // @ts-expect-error Testing with undefined ID
1591
- await expect(node.load(undefined)).rejects.toThrow("Trying to load CoValue with undefined id");
682
+ await expect(client.node.load(undefined)).rejects.toThrow("Trying to load CoValue with undefined id");
1592
683
  });
1593
684
  test("should throw error when trying to load with invalid ID format", async () => {
1594
- const { node } = await createConnectedTestNode();
685
+ const client = await setupTestAccount();
1595
686
  // @ts-expect-error Testing with invalid ID format
1596
- await expect(node.load("invalid_id")).rejects.toThrow("Trying to load CoValue with invalid id invalid_id");
687
+ await expect(client.node.load("invalid_id")).rejects.toThrow("Trying to load CoValue with invalid id invalid_id");
1597
688
  });
1598
689
  });
1599
690
  describe("SyncManager.handleSyncMessage", () => {
1600
691
  test("should ignore messages with undefined ID", async () => {
1601
- const { node: client } = await createConnectedTestNode();
1602
- const peer = client.syncManager.getPeers()[0];
692
+ const client = await setupTestAccount();
693
+ const { peerState } = client.connectToSyncServer();
1603
694
  // Create an invalid message with undefined ID
1604
695
  const invalidMessage = {
1605
696
  action: "load",
@@ -1607,14 +698,14 @@ describe("SyncManager.handleSyncMessage", () => {
1607
698
  header: false,
1608
699
  sessions: {},
1609
700
  };
1610
- await client.syncManager.handleSyncMessage(invalidMessage, peer);
701
+ await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
1611
702
  // Verify that no state changes occurred
1612
- expect(peer.knownStates.has(invalidMessage.id)).toBe(false);
1613
- expect(peer.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
703
+ expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
704
+ expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
1614
705
  });
1615
706
  test("should ignore messages with invalid ID format", async () => {
1616
- const { node: client } = await createConnectedTestNode();
1617
- const peer = client.syncManager.getPeers()[0];
707
+ const client = await setupTestAccount();
708
+ const { peerState } = client.connectToSyncServer();
1618
709
  // Create an invalid message with wrong ID format
1619
710
  const invalidMessage = {
1620
711
  action: "load",
@@ -1622,41 +713,41 @@ describe("SyncManager.handleSyncMessage", () => {
1622
713
  header: false,
1623
714
  sessions: {},
1624
715
  };
1625
- await client.syncManager.handleSyncMessage(invalidMessage, peer);
716
+ await client.node.syncManager.handleSyncMessage(invalidMessage, peerState);
1626
717
  // Verify that no state changes occurred
1627
- expect(peer.knownStates.has(invalidMessage.id)).toBe(false);
1628
- expect(peer.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
718
+ expect(peerState.knownStates.has(invalidMessage.id)).toBe(false);
719
+ expect(peerState.optimisticKnownStates.has(invalidMessage.id)).toBe(false);
1629
720
  });
1630
721
  test("should ignore messages for errored coValues", async () => {
1631
- const { node: client } = await createConnectedTestNode();
1632
- const peer = client.syncManager.getPeers()[0];
722
+ const client = await setupTestAccount();
723
+ const { peerState } = client.connectToSyncServer();
1633
724
  // Add a coValue to the errored set
1634
725
  const erroredId = "co_z123";
1635
- peer.erroredCoValues.set(erroredId, new Error("Test error"));
726
+ peerState.erroredCoValues.set(erroredId, new Error("Test error"));
1636
727
  const message = {
1637
728
  action: "load",
1638
729
  id: erroredId,
1639
730
  header: false,
1640
731
  sessions: {},
1641
732
  };
1642
- await client.syncManager.handleSyncMessage(message, peer);
733
+ await client.node.syncManager.handleSyncMessage(message, peerState);
1643
734
  // Verify that no state changes occurred
1644
- expect(peer.knownStates.has(message.id)).toBe(false);
1645
- expect(peer.optimisticKnownStates.has(message.id)).toBe(false);
735
+ expect(peerState.knownStates.has(message.id)).toBe(false);
736
+ expect(peerState.optimisticKnownStates.has(message.id)).toBe(false);
1646
737
  });
1647
738
  test("should process valid messages", async () => {
1648
- const { node: client } = await createConnectedTestNode();
1649
- const group = client.createGroup();
1650
- const peer = client.syncManager.getPeers()[0];
739
+ const client = await setupTestAccount();
740
+ const { peerState } = client.connectToSyncServer();
741
+ const group = client.node.createGroup();
1651
742
  const validMessage = {
1652
743
  action: "load",
1653
744
  id: group.id,
1654
745
  header: false,
1655
746
  sessions: {},
1656
747
  };
1657
- await client.syncManager.handleSyncMessage(validMessage, peer);
748
+ await client.node.syncManager.handleSyncMessage(validMessage, peerState);
1658
749
  // Verify that the message was processed
1659
- expect(peer.knownStates.has(group.id)).toBe(true);
750
+ expect(peerState.knownStates.has(group.id)).toBe(true);
1660
751
  });
1661
752
  });
1662
753
  //# sourceMappingURL=sync.test.js.map