cojson 0.0.5 → 0.0.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.
@@ -0,0 +1,861 @@
1
+ "use strict";
2
+ import {
3
+ getAgent,
4
+ getAgentID,
5
+ newRandomAgentCredential,
6
+ newRandomSessionID
7
+ } from "./coValue";
8
+ import { LocalNode } from "./node";
9
+ import { expectMap } from "./contentType";
10
+ import {
11
+ ReadableStream,
12
+ WritableStream,
13
+ TransformStream
14
+ } from "isomorphic-streams";
15
+ test(
16
+ "Node replies with initial tx and header to empty subscribe",
17
+ async () => {
18
+ const admin = newRandomAgentCredential("admin");
19
+ const adminID = getAgentID(getAgent(admin));
20
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
21
+ const team = node.createTeam();
22
+ const map = team.createMap();
23
+ map.edit((editable) => {
24
+ editable.set("hello", "world", "trusting");
25
+ });
26
+ const [inRx, inTx] = newStreamPair();
27
+ const [outRx, outTx] = newStreamPair();
28
+ node.sync.addPeer({
29
+ id: "test",
30
+ incoming: inRx,
31
+ outgoing: outTx,
32
+ role: "peer"
33
+ });
34
+ const writer = inTx.getWriter();
35
+ await writer.write({
36
+ action: "subscribe",
37
+ coValueID: map.coValue.id,
38
+ header: false,
39
+ sessions: {}
40
+ });
41
+ const reader = outRx.getReader();
42
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
43
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
44
+ const mapTellKnownStateMsg = await reader.read();
45
+ expect(mapTellKnownStateMsg.value).toEqual({
46
+ action: "tellKnownState",
47
+ ...map.coValue.knownState()
48
+ });
49
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
50
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
51
+ const newContentMsg = await reader.read();
52
+ expect(newContentMsg.value).toEqual({
53
+ action: "newContent",
54
+ coValueID: map.coValue.id,
55
+ header: {
56
+ type: "comap",
57
+ ruleset: { type: "ownedByTeam", team: team.id },
58
+ meta: null,
59
+ createdAt: map.coValue.header.createdAt,
60
+ uniqueness: map.coValue.header.uniqueness,
61
+ publicNickname: "map"
62
+ },
63
+ newContent: {
64
+ [node.ownSessionID]: {
65
+ after: 0,
66
+ newTransactions: [
67
+ {
68
+ privacy: "trusting",
69
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[0].madeAt,
70
+ changes: [
71
+ {
72
+ op: "insert",
73
+ key: "hello",
74
+ value: "world"
75
+ }
76
+ ]
77
+ }
78
+ ],
79
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
80
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
81
+ }
82
+ }
83
+ });
84
+ }
85
+ );
86
+ test("Node replies with only new tx to subscribe with some known state", async () => {
87
+ const admin = newRandomAgentCredential("admin");
88
+ const adminID = getAgentID(getAgent(admin));
89
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
90
+ const team = node.createTeam();
91
+ const map = team.createMap();
92
+ map.edit((editable) => {
93
+ editable.set("hello", "world", "trusting");
94
+ editable.set("goodbye", "world", "trusting");
95
+ });
96
+ const [inRx, inTx] = newStreamPair();
97
+ const [outRx, outTx] = newStreamPair();
98
+ node.sync.addPeer({
99
+ id: "test",
100
+ incoming: inRx,
101
+ outgoing: outTx,
102
+ role: "peer"
103
+ });
104
+ const writer = inTx.getWriter();
105
+ await writer.write({
106
+ action: "subscribe",
107
+ coValueID: map.coValue.id,
108
+ header: true,
109
+ sessions: {
110
+ [node.ownSessionID]: 1
111
+ }
112
+ });
113
+ const reader = outRx.getReader();
114
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
115
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
116
+ const mapTellKnownStateMsg = await reader.read();
117
+ expect(mapTellKnownStateMsg.value).toEqual({
118
+ action: "tellKnownState",
119
+ ...map.coValue.knownState()
120
+ });
121
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
122
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
123
+ const mapNewContentMsg = await reader.read();
124
+ expect(mapNewContentMsg.value).toEqual({
125
+ action: "newContent",
126
+ coValueID: map.coValue.id,
127
+ header: void 0,
128
+ newContent: {
129
+ [node.ownSessionID]: {
130
+ after: 1,
131
+ newTransactions: [
132
+ {
133
+ privacy: "trusting",
134
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[1].madeAt,
135
+ changes: [
136
+ {
137
+ op: "insert",
138
+ key: "goodbye",
139
+ value: "world"
140
+ }
141
+ ]
142
+ }
143
+ ],
144
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
145
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
146
+ }
147
+ }
148
+ });
149
+ });
150
+ test.todo(
151
+ "TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues"
152
+ );
153
+ test("After subscribing, node sends own known state and new txs to peer", async () => {
154
+ const admin = newRandomAgentCredential("admin");
155
+ const adminID = getAgentID(getAgent(admin));
156
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
157
+ const team = node.createTeam();
158
+ const map = team.createMap();
159
+ const [inRx, inTx] = newStreamPair();
160
+ const [outRx, outTx] = newStreamPair();
161
+ node.sync.addPeer({
162
+ id: "test",
163
+ incoming: inRx,
164
+ outgoing: outTx,
165
+ role: "peer"
166
+ });
167
+ const writer = inTx.getWriter();
168
+ await writer.write({
169
+ action: "subscribe",
170
+ coValueID: map.coValue.id,
171
+ header: false,
172
+ sessions: {
173
+ [node.ownSessionID]: 0
174
+ }
175
+ });
176
+ const reader = outRx.getReader();
177
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
178
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
179
+ const mapTellKnownStateMsg = await reader.read();
180
+ expect(mapTellKnownStateMsg.value).toEqual({
181
+ action: "tellKnownState",
182
+ ...map.coValue.knownState()
183
+ });
184
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
185
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
186
+ const mapNewContentHeaderOnlyMsg = await reader.read();
187
+ expect(mapNewContentHeaderOnlyMsg.value).toEqual({
188
+ action: "newContent",
189
+ coValueID: map.coValue.id,
190
+ header: map.coValue.header,
191
+ newContent: {}
192
+ });
193
+ map.edit((editable) => {
194
+ editable.set("hello", "world", "trusting");
195
+ });
196
+ const mapEditMsg1 = await reader.read();
197
+ expect(mapEditMsg1.value).toEqual({
198
+ action: "newContent",
199
+ coValueID: map.coValue.id,
200
+ newContent: {
201
+ [node.ownSessionID]: {
202
+ after: 0,
203
+ newTransactions: [
204
+ {
205
+ privacy: "trusting",
206
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[0].madeAt,
207
+ changes: [
208
+ {
209
+ op: "insert",
210
+ key: "hello",
211
+ value: "world"
212
+ }
213
+ ]
214
+ }
215
+ ],
216
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
217
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
218
+ }
219
+ }
220
+ });
221
+ map.edit((editable) => {
222
+ editable.set("goodbye", "world", "trusting");
223
+ });
224
+ const mapEditMsg2 = await reader.read();
225
+ expect(mapEditMsg2.value).toEqual({
226
+ action: "newContent",
227
+ coValueID: map.coValue.id,
228
+ newContent: {
229
+ [node.ownSessionID]: {
230
+ after: 1,
231
+ newTransactions: [
232
+ {
233
+ privacy: "trusting",
234
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[1].madeAt,
235
+ changes: [
236
+ {
237
+ op: "insert",
238
+ key: "goodbye",
239
+ value: "world"
240
+ }
241
+ ]
242
+ }
243
+ ],
244
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
245
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
246
+ }
247
+ }
248
+ });
249
+ });
250
+ test("Client replies with known new content to tellKnownState from server", async () => {
251
+ const admin = newRandomAgentCredential("admin");
252
+ const adminID = getAgentID(getAgent(admin));
253
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
254
+ const team = node.createTeam();
255
+ const map = team.createMap();
256
+ map.edit((editable) => {
257
+ editable.set("hello", "world", "trusting");
258
+ });
259
+ const [inRx, inTx] = newStreamPair();
260
+ const [outRx, outTx] = newStreamPair();
261
+ node.sync.addPeer({
262
+ id: "test",
263
+ incoming: inRx,
264
+ outgoing: outTx,
265
+ role: "peer"
266
+ });
267
+ const reader = outRx.getReader();
268
+ const writer = inTx.getWriter();
269
+ await writer.write({
270
+ action: "tellKnownState",
271
+ coValueID: map.coValue.id,
272
+ header: false,
273
+ sessions: {
274
+ [node.ownSessionID]: 0
275
+ }
276
+ });
277
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
278
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
279
+ const mapTellKnownStateMsg = await reader.read();
280
+ expect(mapTellKnownStateMsg.value).toEqual({
281
+ action: "tellKnownState",
282
+ ...map.coValue.knownState()
283
+ });
284
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
285
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
286
+ const mapNewContentMsg = await reader.read();
287
+ expect(mapNewContentMsg.value).toEqual({
288
+ action: "newContent",
289
+ coValueID: map.coValue.id,
290
+ header: map.coValue.header,
291
+ newContent: {
292
+ [node.ownSessionID]: {
293
+ after: 0,
294
+ newTransactions: [
295
+ {
296
+ privacy: "trusting",
297
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[0].madeAt,
298
+ changes: [
299
+ {
300
+ op: "insert",
301
+ key: "hello",
302
+ value: "world"
303
+ }
304
+ ]
305
+ }
306
+ ],
307
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
308
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
309
+ }
310
+ }
311
+ });
312
+ });
313
+ test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => {
314
+ const admin = newRandomAgentCredential("admin");
315
+ const adminID = getAgentID(getAgent(admin));
316
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
317
+ const team = node.createTeam();
318
+ const map = team.createMap();
319
+ const [inRx, inTx] = newStreamPair();
320
+ const [outRx, outTx] = newStreamPair();
321
+ node.sync.addPeer({
322
+ id: "test",
323
+ incoming: inRx,
324
+ outgoing: outTx,
325
+ role: "peer"
326
+ });
327
+ const writer = inTx.getWriter();
328
+ await writer.write({
329
+ action: "subscribe",
330
+ coValueID: map.coValue.id,
331
+ header: false,
332
+ sessions: {
333
+ [node.ownSessionID]: 0
334
+ }
335
+ });
336
+ const reader = outRx.getReader();
337
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
338
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
339
+ const mapTellKnownStateMsg = await reader.read();
340
+ expect(mapTellKnownStateMsg.value).toEqual({
341
+ action: "tellKnownState",
342
+ ...map.coValue.knownState()
343
+ });
344
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
345
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
346
+ const mapNewContentHeaderOnlyMsg = await reader.read();
347
+ expect(mapNewContentHeaderOnlyMsg.value).toEqual({
348
+ action: "newContent",
349
+ coValueID: map.coValue.id,
350
+ header: map.coValue.header,
351
+ newContent: {}
352
+ });
353
+ map.edit((editable) => {
354
+ editable.set("hello", "world", "trusting");
355
+ });
356
+ map.edit((editable) => {
357
+ editable.set("goodbye", "world", "trusting");
358
+ });
359
+ const _mapEditMsg1 = await reader.read();
360
+ const _mapEditMsg2 = await reader.read();
361
+ await writer.write({
362
+ action: "wrongAssumedKnownState",
363
+ coValueID: map.coValue.id,
364
+ header: true,
365
+ sessions: {
366
+ [node.ownSessionID]: 1
367
+ }
368
+ });
369
+ const newContentAfterWrongAssumedState = await reader.read();
370
+ expect(newContentAfterWrongAssumedState.value).toEqual({
371
+ action: "newContent",
372
+ coValueID: map.coValue.id,
373
+ header: void 0,
374
+ newContent: {
375
+ [node.ownSessionID]: {
376
+ after: 1,
377
+ newTransactions: [
378
+ {
379
+ privacy: "trusting",
380
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[1].madeAt,
381
+ changes: [
382
+ {
383
+ op: "insert",
384
+ key: "goodbye",
385
+ value: "world"
386
+ }
387
+ ]
388
+ }
389
+ ],
390
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
391
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
392
+ }
393
+ }
394
+ });
395
+ });
396
+ test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
397
+ const admin = newRandomAgentCredential("admin");
398
+ const adminID = getAgentID(getAgent(admin));
399
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
400
+ const team = node.createTeam();
401
+ const map = team.createMap();
402
+ const [inRx, _inTx] = newStreamPair();
403
+ const [outRx, outTx] = newStreamPair();
404
+ node.sync.addPeer({
405
+ id: "test",
406
+ incoming: inRx,
407
+ outgoing: outTx,
408
+ role: "peer"
409
+ });
410
+ map.edit((editable) => {
411
+ editable.set("hello", "world", "trusting");
412
+ });
413
+ const reader = outRx.getReader();
414
+ await expect(shouldNotResolve(reader.read(), { timeout: 100 })).resolves.toBeUndefined();
415
+ });
416
+ test("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", async () => {
417
+ const admin = newRandomAgentCredential("admin");
418
+ const adminID = getAgentID(getAgent(admin));
419
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
420
+ const team = node.createTeam();
421
+ const map = team.createMap();
422
+ const [inRx, _inTx] = newStreamPair();
423
+ const [outRx, outTx] = newStreamPair();
424
+ node.sync.addPeer({
425
+ id: "test",
426
+ incoming: inRx,
427
+ outgoing: outTx,
428
+ role: "server"
429
+ });
430
+ const reader = outRx.getReader();
431
+ expect((await reader.read()).value).toMatchObject({
432
+ action: "subscribe",
433
+ coValueID: adminID
434
+ });
435
+ expect((await reader.read()).value).toMatchObject({
436
+ action: "subscribe",
437
+ coValueID: team.teamMap.coValue.id
438
+ });
439
+ const mapSubscribeMsg = await reader.read();
440
+ expect(mapSubscribeMsg.value).toEqual({
441
+ action: "subscribe",
442
+ coValueID: map.coValue.id,
443
+ header: true,
444
+ sessions: {}
445
+ });
446
+ map.edit((editable) => {
447
+ editable.set("hello", "world", "trusting");
448
+ });
449
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
450
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
451
+ const mapNewContentMsg = await reader.read();
452
+ expect(mapNewContentMsg.value).toEqual({
453
+ action: "newContent",
454
+ coValueID: map.coValue.id,
455
+ header: map.coValue.header,
456
+ newContent: {
457
+ [node.ownSessionID]: {
458
+ after: 0,
459
+ newTransactions: [
460
+ {
461
+ privacy: "trusting",
462
+ madeAt: map.coValue.sessions[node.ownSessionID].transactions[0].madeAt,
463
+ changes: [
464
+ {
465
+ op: "insert",
466
+ key: "hello",
467
+ value: "world"
468
+ }
469
+ ]
470
+ }
471
+ ],
472
+ lastHash: map.coValue.sessions[node.ownSessionID].lastHash,
473
+ lastSignature: map.coValue.sessions[node.ownSessionID].lastSignature
474
+ }
475
+ }
476
+ });
477
+ });
478
+ test("If we add a server peer, newly created coValues are auto-subscribed to", async () => {
479
+ const admin = newRandomAgentCredential("admin");
480
+ const adminID = getAgentID(getAgent(admin));
481
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
482
+ const team = node.createTeam();
483
+ const [inRx, _inTx] = newStreamPair();
484
+ const [outRx, outTx] = newStreamPair();
485
+ node.sync.addPeer({
486
+ id: "test",
487
+ incoming: inRx,
488
+ outgoing: outTx,
489
+ role: "server"
490
+ });
491
+ const reader = outRx.getReader();
492
+ expect((await reader.read()).value).toMatchObject({
493
+ action: "subscribe",
494
+ coValueID: adminID
495
+ });
496
+ expect((await reader.read()).value).toMatchObject({
497
+ action: "subscribe",
498
+ coValueID: team.teamMap.coValue.id
499
+ });
500
+ const map = team.createMap();
501
+ const mapSubscribeMsg = await reader.read();
502
+ expect(mapSubscribeMsg.value).toEqual({
503
+ action: "subscribe",
504
+ ...map.coValue.knownState()
505
+ });
506
+ expect((await reader.read()).value).toMatchObject(admContEx(adminID));
507
+ expect((await reader.read()).value).toMatchObject(teamContentEx(team));
508
+ const mapContentMsg = await reader.read();
509
+ expect(mapContentMsg.value).toEqual({
510
+ action: "newContent",
511
+ coValueID: map.coValue.id,
512
+ header: map.coValue.header,
513
+ newContent: {}
514
+ });
515
+ });
516
+ test.todo(
517
+ "TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it"
518
+ );
519
+ test("When we connect a new server peer, we try to sync all existing coValues to it", async () => {
520
+ const admin = newRandomAgentCredential("admin");
521
+ const adminID = getAgentID(getAgent(admin));
522
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
523
+ const team = node.createTeam();
524
+ const map = team.createMap();
525
+ const [inRx, _inTx] = newStreamPair();
526
+ const [outRx, outTx] = newStreamPair();
527
+ node.sync.addPeer({
528
+ id: "test",
529
+ incoming: inRx,
530
+ outgoing: outTx,
531
+ role: "server"
532
+ });
533
+ const reader = outRx.getReader();
534
+ const _adminSubscribeMessage = await reader.read();
535
+ const teamSubscribeMessage = await reader.read();
536
+ expect(teamSubscribeMessage.value).toEqual({
537
+ action: "subscribe",
538
+ ...team.teamMap.coValue.knownState()
539
+ });
540
+ const secondMessage = await reader.read();
541
+ expect(secondMessage.value).toEqual({
542
+ action: "subscribe",
543
+ ...map.coValue.knownState()
544
+ });
545
+ });
546
+ 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 () => {
547
+ const admin = newRandomAgentCredential("admin");
548
+ const adminID = getAgentID(getAgent(admin));
549
+ const node = new LocalNode(admin, newRandomSessionID(adminID));
550
+ const team = node.createTeam();
551
+ const map = team.createMap();
552
+ const [inRx, inTx] = newStreamPair();
553
+ const [outRx, outTx] = newStreamPair();
554
+ node.sync.addPeer({
555
+ id: "test",
556
+ incoming: inRx,
557
+ outgoing: outTx,
558
+ role: "peer"
559
+ });
560
+ const writer = inTx.getWriter();
561
+ await writer.write({
562
+ action: "subscribe",
563
+ coValueID: map.coValue.id,
564
+ header: true,
565
+ sessions: {
566
+ [node.ownSessionID]: 1
567
+ }
568
+ });
569
+ const reader = outRx.getReader();
570
+ expect((await reader.read()).value).toMatchObject(admStateEx(adminID));
571
+ expect((await reader.read()).value).toMatchObject(teamStateEx(team));
572
+ const mapTellKnownState = await reader.read();
573
+ expect(mapTellKnownState.value).toEqual({
574
+ action: "tellKnownState",
575
+ ...map.coValue.knownState()
576
+ });
577
+ });
578
+ test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => {
579
+ var _a;
580
+ const admin = newRandomAgentCredential("admin");
581
+ const adminID = getAgentID(getAgent(admin));
582
+ const node1 = new LocalNode(admin, newRandomSessionID(adminID));
583
+ const team = node1.createTeam();
584
+ const [inRx1, inTx1] = newStreamPair();
585
+ const [outRx1, outTx1] = newStreamPair();
586
+ node1.sync.addPeer({
587
+ id: "test2",
588
+ incoming: inRx1,
589
+ outgoing: outTx1,
590
+ role: "server"
591
+ });
592
+ const to1 = inTx1.getWriter();
593
+ const from1 = outRx1.getReader();
594
+ const node2 = new LocalNode(admin, newRandomSessionID(adminID));
595
+ const [inRx2, inTx2] = newStreamPair();
596
+ const [outRx2, outTx2] = newStreamPair();
597
+ node2.sync.addPeer({
598
+ id: "test1",
599
+ incoming: inRx2,
600
+ outgoing: outTx2,
601
+ role: "client"
602
+ });
603
+ const to2 = inTx2.getWriter();
604
+ const from2 = outRx2.getReader();
605
+ const adminSubscribeMessage = await from1.read();
606
+ expect(adminSubscribeMessage.value).toMatchObject({
607
+ action: "subscribe",
608
+ coValueID: adminID
609
+ });
610
+ const teamSubscribeMsg = await from1.read();
611
+ expect(teamSubscribeMsg.value).toMatchObject({
612
+ action: "subscribe",
613
+ coValueID: team.teamMap.coValue.id
614
+ });
615
+ await to2.write(adminSubscribeMessage.value);
616
+ await to2.write(teamSubscribeMsg.value);
617
+ const adminTellKnownStateMsg = await from2.read();
618
+ expect(adminTellKnownStateMsg.value).toMatchObject(admStateEx(adminID));
619
+ const teamTellKnownStateMsg = await from2.read();
620
+ expect(teamTellKnownStateMsg.value).toMatchObject(teamStateEx(team));
621
+ expect(
622
+ node2.sync.peers["test1"].optimisticKnownStates[team.teamMap.coValue.id]
623
+ ).toBeDefined();
624
+ await to1.write(adminTellKnownStateMsg.value);
625
+ await to1.write(teamTellKnownStateMsg.value);
626
+ const adminContentMsg = await from1.read();
627
+ expect(adminContentMsg.value).toMatchObject(admContEx(adminID));
628
+ const teamContentMsg = await from1.read();
629
+ expect(teamContentMsg.value).toMatchObject(teamContentEx(team));
630
+ await to2.write(adminContentMsg.value);
631
+ await to2.write(teamContentMsg.value);
632
+ const map = team.createMap();
633
+ const mapSubscriptionMsg = await from1.read();
634
+ expect(mapSubscriptionMsg.value).toMatchObject({
635
+ action: "subscribe",
636
+ coValueID: map.coValue.id
637
+ });
638
+ const mapNewContentMsg = await from1.read();
639
+ expect(mapNewContentMsg.value).toEqual({
640
+ action: "newContent",
641
+ coValueID: map.coValue.id,
642
+ header: map.coValue.header,
643
+ newContent: {}
644
+ });
645
+ await to2.write(mapSubscriptionMsg.value);
646
+ const mapTellKnownStateMsg = await from2.read();
647
+ expect(mapTellKnownStateMsg.value).toEqual({
648
+ action: "tellKnownState",
649
+ coValueID: map.coValue.id,
650
+ header: false,
651
+ sessions: {}
652
+ });
653
+ expect((_a = node2.coValues[map.coValue.id]) == null ? void 0 : _a.state).toEqual("loading");
654
+ await to2.write(mapNewContentMsg.value);
655
+ map.edit((editable) => {
656
+ editable.set("hello", "world", "trusting");
657
+ });
658
+ const mapEditMsg = await from1.read();
659
+ await to2.write(mapEditMsg.value);
660
+ await new Promise((resolve) => setTimeout(resolve, 100));
661
+ expect(
662
+ expectMap(
663
+ node2.expectCoValueLoaded(map.coValue.id).getCurrentContent()
664
+ ).get("hello")
665
+ ).toEqual("world");
666
+ });
667
+ 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 () => {
668
+ const admin = newRandomAgentCredential("admin");
669
+ const adminID = getAgentID(getAgent(admin));
670
+ const node1 = new LocalNode(admin, newRandomSessionID(adminID));
671
+ const team = node1.createTeam();
672
+ const map = team.createMap();
673
+ map.edit((editable) => {
674
+ editable.set("hello", "world", "trusting");
675
+ });
676
+ const node2 = new LocalNode(admin, newRandomSessionID(adminID));
677
+ const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2");
678
+ node1.sync.addPeer(node2asPeer);
679
+ node2.sync.addPeer(node1asPeer);
680
+ await node2.loadCoValue(map.coValue.id);
681
+ expect(
682
+ expectMap(
683
+ node2.expectCoValueLoaded(map.coValue.id).getCurrentContent()
684
+ ).get("hello")
685
+ ).toEqual("world");
686
+ });
687
+ test("Can sync a coValue through a server to another client", async () => {
688
+ const admin = newRandomAgentCredential("admin");
689
+ const adminID = getAgentID(getAgent(admin));
690
+ const client1 = new LocalNode(admin, newRandomSessionID(adminID));
691
+ const team = client1.createTeam();
692
+ const map = team.createMap();
693
+ map.edit((editable) => {
694
+ editable.set("hello", "world", "trusting");
695
+ });
696
+ const serverUser = newRandomAgentCredential("serverUser");
697
+ const serverUserID = getAgentID(getAgent(serverUser));
698
+ const server = new LocalNode(serverUser, newRandomSessionID(serverUserID));
699
+ const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
700
+ peer1role: "server",
701
+ peer2role: "client"
702
+ });
703
+ client1.sync.addPeer(serverAsPeer);
704
+ server.sync.addPeer(client1AsPeer);
705
+ const client2 = new LocalNode(admin, newRandomSessionID(adminID));
706
+ const [serverAsOtherPeer, client2AsPeer] = connectedPeers(
707
+ "server",
708
+ "client2",
709
+ { peer1role: "server", peer2role: "client" }
710
+ );
711
+ client2.sync.addPeer(serverAsOtherPeer);
712
+ server.sync.addPeer(client2AsPeer);
713
+ const mapOnClient2 = await client2.loadCoValue(map.coValue.id);
714
+ expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual(
715
+ "world"
716
+ );
717
+ });
718
+ test("Can sync a coValue with private transactions through a server to another client", async () => {
719
+ const admin = newRandomAgentCredential("admin");
720
+ const adminID = getAgentID(getAgent(admin));
721
+ const client1 = new LocalNode(admin, newRandomSessionID(adminID));
722
+ const team = client1.createTeam();
723
+ const map = team.createMap();
724
+ map.edit((editable) => {
725
+ editable.set("hello", "world", "private");
726
+ });
727
+ const serverUser = newRandomAgentCredential("serverUser");
728
+ const serverUserID = getAgentID(getAgent(serverUser));
729
+ const server = new LocalNode(serverUser, newRandomSessionID(serverUserID));
730
+ const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
731
+ trace: true,
732
+ peer1role: "server",
733
+ peer2role: "client"
734
+ });
735
+ client1.sync.addPeer(serverAsPeer);
736
+ server.sync.addPeer(client1AsPeer);
737
+ const client2 = new LocalNode(admin, newRandomSessionID(adminID));
738
+ const [serverAsOtherPeer, client2AsPeer] = connectedPeers(
739
+ "server",
740
+ "client2",
741
+ { trace: true, peer1role: "server", peer2role: "client" }
742
+ );
743
+ client2.sync.addPeer(serverAsOtherPeer);
744
+ server.sync.addPeer(client2AsPeer);
745
+ const mapOnClient2 = await client2.loadCoValue(map.coValue.id);
746
+ expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual(
747
+ "world"
748
+ );
749
+ });
750
+ function teamContentEx(team) {
751
+ return {
752
+ action: "newContent",
753
+ coValueID: team.teamMap.coValue.id
754
+ };
755
+ }
756
+ function admContEx(adminID) {
757
+ return {
758
+ action: "newContent",
759
+ coValueID: adminID
760
+ };
761
+ }
762
+ function teamStateEx(team) {
763
+ return {
764
+ action: "tellKnownState",
765
+ coValueID: team.teamMap.coValue.id
766
+ };
767
+ }
768
+ function admStateEx(adminID) {
769
+ return {
770
+ action: "tellKnownState",
771
+ coValueID: adminID
772
+ };
773
+ }
774
+ function newStreamPair() {
775
+ const queue = [];
776
+ let resolveNextItemReady = () => {
777
+ };
778
+ let nextItemReady = new Promise((resolve) => {
779
+ resolveNextItemReady = resolve;
780
+ });
781
+ const readable = new ReadableStream({
782
+ async pull(controller) {
783
+ let retriesLeft = 3;
784
+ while (retriesLeft > 0) {
785
+ retriesLeft--;
786
+ if (queue.length > 0) {
787
+ controller.enqueue(queue.shift());
788
+ if (queue.length === 0) {
789
+ nextItemReady = new Promise((resolve) => {
790
+ resolveNextItemReady = resolve;
791
+ });
792
+ }
793
+ return;
794
+ } else {
795
+ await nextItemReady;
796
+ }
797
+ }
798
+ throw new Error("Should only use one retry to get next item in queue.");
799
+ }
800
+ });
801
+ const writable = new WritableStream({
802
+ write(chunk) {
803
+ queue.push(chunk);
804
+ if (queue.length === 1) {
805
+ process.nextTick(() => resolveNextItemReady());
806
+ }
807
+ }
808
+ });
809
+ return [readable, writable];
810
+ }
811
+ function shouldNotResolve(promise, ops) {
812
+ return new Promise((resolve, reject) => {
813
+ promise.then(
814
+ (v) => reject(
815
+ new Error(
816
+ "Should not have resolved, but resolved to " + JSON.stringify(v)
817
+ )
818
+ )
819
+ ).catch(reject);
820
+ setTimeout(resolve, ops.timeout);
821
+ });
822
+ }
823
+ function connectedPeers(peer1id, peer2id, {
824
+ trace = false,
825
+ peer1role = "peer",
826
+ peer2role = "peer"
827
+ } = {}) {
828
+ const [inRx1, inTx1] = newStreamPair();
829
+ const [outRx1, outTx1] = newStreamPair();
830
+ const [inRx2, inTx2] = newStreamPair();
831
+ const [outRx2, outTx2] = newStreamPair();
832
+ void outRx2.pipeThrough(
833
+ new TransformStream({
834
+ transform(chunk, controller) {
835
+ trace && console.log(`${peer2id} -> ${peer1id}`, chunk);
836
+ controller.enqueue(chunk);
837
+ }
838
+ })
839
+ ).pipeTo(inTx1);
840
+ void outRx1.pipeThrough(
841
+ new TransformStream({
842
+ transform(chunk, controller) {
843
+ trace && console.log(`${peer1id} -> ${peer2id}`, chunk);
844
+ controller.enqueue(chunk);
845
+ }
846
+ })
847
+ ).pipeTo(inTx2);
848
+ const peer2AsPeer = {
849
+ id: peer2id,
850
+ incoming: inRx1,
851
+ outgoing: outTx1,
852
+ role: peer2role
853
+ };
854
+ const peer1AsPeer = {
855
+ id: peer1id,
856
+ incoming: inRx2,
857
+ outgoing: outTx2,
858
+ role: peer1role
859
+ };
860
+ return [peer1AsPeer, peer2AsPeer];
861
+ }