cojson 0.18.5 → 0.18.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/coValueCore/branching.d.ts +5 -5
- package/dist/coValueCore/branching.d.ts.map +1 -1
- package/dist/coValueCore/branching.js +72 -4
- package/dist/coValueCore/branching.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +4 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +37 -16
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/coList.d.ts +8 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +86 -58
- package/dist/coValues/coList.js.map +1 -1
- package/dist/exports.d.ts +2 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +1 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +1 -0
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +7 -4
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +7 -4
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/tests/branching.test.js +65 -1
- package/dist/tests/branching.test.js.map +1 -1
- package/dist/tests/coList.test.js +367 -1
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +1 -1
- package/dist/tests/sync.load.test.js +2 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +6 -10
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +2 -2
- package/package.json +2 -2
- package/src/coValueCore/branching.ts +109 -9
- package/src/coValueCore/coValueCore.ts +45 -17
- package/src/coValues/coList.ts +118 -78
- package/src/exports.ts +6 -1
- package/src/ids.ts +1 -0
- package/src/storage/storageAsync.ts +8 -4
- package/src/storage/storageSync.ts +8 -4
- package/src/tests/branching.test.ts +103 -1
- package/src/tests/coList.test.ts +529 -1
- package/src/tests/sync.load.test.ts +2 -2
- package/src/tests/sync.storageAsync.test.ts +9 -12
package/src/tests/coList.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, expect, test } from "vitest";
|
|
1
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
2
|
import { expectList } from "../coValue.js";
|
|
3
3
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
4
4
|
import { LocalNode } from "../localNode.js";
|
|
@@ -428,3 +428,531 @@ test("Should ignore unknown meta transactions", () => {
|
|
|
428
428
|
|
|
429
429
|
expect(content.toJSON()).toEqual(["first"]);
|
|
430
430
|
});
|
|
431
|
+
|
|
432
|
+
describe("CoList Branching", () => {
|
|
433
|
+
test("should handle concurrent appends from multiple branches", async () => {
|
|
434
|
+
const client1 = setupTestNode({
|
|
435
|
+
connected: true,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const group = client1.node.createGroup();
|
|
439
|
+
group.addMember("everyone", "writer");
|
|
440
|
+
|
|
441
|
+
const groceryList = group.createList(["milk"]);
|
|
442
|
+
|
|
443
|
+
// Create branches for each client
|
|
444
|
+
const aliceBranch = expectList(
|
|
445
|
+
groceryList.core
|
|
446
|
+
.createBranch("alice-branch", group.id)
|
|
447
|
+
.getCurrentContent(),
|
|
448
|
+
);
|
|
449
|
+
const bobBranch = expectList(
|
|
450
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Both branches append grocery items
|
|
454
|
+
aliceBranch.append("bread", undefined, "trusting");
|
|
455
|
+
aliceBranch.append("butter", undefined, "trusting");
|
|
456
|
+
bobBranch.append("eggs", undefined, "trusting");
|
|
457
|
+
|
|
458
|
+
// Merge both branches back to source
|
|
459
|
+
aliceBranch.core.mergeBranch();
|
|
460
|
+
bobBranch.core.mergeBranch();
|
|
461
|
+
|
|
462
|
+
// Wait for sync
|
|
463
|
+
await groceryList.core.waitForSync();
|
|
464
|
+
|
|
465
|
+
// Source list should contain all items from both branches
|
|
466
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
467
|
+
[
|
|
468
|
+
"milk",
|
|
469
|
+
"eggs",
|
|
470
|
+
"bread",
|
|
471
|
+
"butter",
|
|
472
|
+
]
|
|
473
|
+
`);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("should handle concurrent prepends from multiple branches", async () => {
|
|
477
|
+
const client1 = setupTestNode({
|
|
478
|
+
connected: true,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const group = client1.node.createGroup();
|
|
482
|
+
group.addMember("everyone", "writer");
|
|
483
|
+
|
|
484
|
+
const groceryList = group.createList(["milk"]);
|
|
485
|
+
|
|
486
|
+
// Create branches for each client
|
|
487
|
+
const aliceBranch = expectList(
|
|
488
|
+
groceryList.core
|
|
489
|
+
.createBranch("alice-branch", group.id)
|
|
490
|
+
.getCurrentContent(),
|
|
491
|
+
);
|
|
492
|
+
const bobBranch = expectList(
|
|
493
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
// Both branches prepend grocery items
|
|
497
|
+
aliceBranch.prepend("bread", undefined, "trusting");
|
|
498
|
+
aliceBranch.prepend("butter", undefined, "trusting");
|
|
499
|
+
bobBranch.prepend("eggs", undefined, "trusting");
|
|
500
|
+
|
|
501
|
+
// Merge both branches back to source
|
|
502
|
+
aliceBranch.core.mergeBranch();
|
|
503
|
+
bobBranch.core.mergeBranch();
|
|
504
|
+
|
|
505
|
+
// Wait for sync
|
|
506
|
+
await groceryList.core.waitForSync();
|
|
507
|
+
|
|
508
|
+
// Source list should contain all items from both branches
|
|
509
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
510
|
+
[
|
|
511
|
+
"eggs",
|
|
512
|
+
"butter",
|
|
513
|
+
"bread",
|
|
514
|
+
"milk",
|
|
515
|
+
]
|
|
516
|
+
`);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("should handle concurrent deletes from multiple branches", async () => {
|
|
520
|
+
const client1 = setupTestNode({
|
|
521
|
+
connected: true,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const group = client1.node.createGroup();
|
|
525
|
+
group.addMember("everyone", "writer");
|
|
526
|
+
|
|
527
|
+
const groceryList = group.createList(["milk", "bread", "eggs", "cheese"]);
|
|
528
|
+
|
|
529
|
+
// Create branches for each client
|
|
530
|
+
const aliceBranch = expectList(
|
|
531
|
+
groceryList.core
|
|
532
|
+
.createBranch("alice-branch", group.id)
|
|
533
|
+
.getCurrentContent(),
|
|
534
|
+
);
|
|
535
|
+
const bobBranch = expectList(
|
|
536
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// Both branches delete different grocery items
|
|
540
|
+
aliceBranch.delete(aliceBranch.asArray().indexOf("bread"), "trusting");
|
|
541
|
+
bobBranch.delete(bobBranch.asArray().indexOf("eggs"), "trusting");
|
|
542
|
+
|
|
543
|
+
// Merge both branches back to source
|
|
544
|
+
aliceBranch.core.mergeBranch();
|
|
545
|
+
bobBranch.core.mergeBranch();
|
|
546
|
+
|
|
547
|
+
// Wait for sync
|
|
548
|
+
await groceryList.core.waitForSync();
|
|
549
|
+
|
|
550
|
+
// Source list should reflect both deletions
|
|
551
|
+
expect(groceryList.toJSON()).toEqual(["milk", "cheese"]);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test("should handle concurrent insertions at different positions from branches", async () => {
|
|
555
|
+
const client1 = setupTestNode({
|
|
556
|
+
connected: true,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const group = client1.node.createGroup();
|
|
560
|
+
group.addMember("everyone", "writer");
|
|
561
|
+
|
|
562
|
+
const groceryList = group.createList(["milk", "cheese", "butter"]);
|
|
563
|
+
|
|
564
|
+
// Create branches for each client
|
|
565
|
+
const aliceBranch = expectList(
|
|
566
|
+
groceryList.core
|
|
567
|
+
.createBranch("alice-branch", group.id)
|
|
568
|
+
.getCurrentContent(),
|
|
569
|
+
);
|
|
570
|
+
const bobBranch = expectList(
|
|
571
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
// Both branches insert grocery items at position 1
|
|
575
|
+
aliceBranch.append("bread", 0, "trusting");
|
|
576
|
+
bobBranch.append("eggs", 0, "trusting");
|
|
577
|
+
|
|
578
|
+
// Merge both branches back to source
|
|
579
|
+
aliceBranch.core.mergeBranch();
|
|
580
|
+
bobBranch.core.mergeBranch();
|
|
581
|
+
|
|
582
|
+
// Wait for sync
|
|
583
|
+
await groceryList.core.waitForSync();
|
|
584
|
+
|
|
585
|
+
// Source list should contain both insertions
|
|
586
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
587
|
+
[
|
|
588
|
+
"milk",
|
|
589
|
+
"eggs",
|
|
590
|
+
"bread",
|
|
591
|
+
"cheese",
|
|
592
|
+
"butter",
|
|
593
|
+
]
|
|
594
|
+
`);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("should handle multiple branches modifying different list items", async () => {
|
|
598
|
+
const client1 = setupTestNode({
|
|
599
|
+
connected: true,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const group = client1.node.createGroup();
|
|
603
|
+
group.addMember("everyone", "writer");
|
|
604
|
+
|
|
605
|
+
const groceryList = group.createList(["milk", "bread", "eggs"]);
|
|
606
|
+
|
|
607
|
+
// Create branches for each client
|
|
608
|
+
const aliceBranch = expectList(
|
|
609
|
+
groceryList.core
|
|
610
|
+
.createBranch("alice-branch", group.id)
|
|
611
|
+
.getCurrentContent(),
|
|
612
|
+
);
|
|
613
|
+
const bobBranch = expectList(
|
|
614
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
615
|
+
);
|
|
616
|
+
const charlieBranch = expectList(
|
|
617
|
+
groceryList.core
|
|
618
|
+
.createBranch("charlie-branch", group.id)
|
|
619
|
+
.getCurrentContent(),
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
// Each branch modifies different grocery items
|
|
623
|
+
aliceBranch.replace(0, "organic milk", "trusting");
|
|
624
|
+
bobBranch.replace(1, "whole wheat bread", "trusting");
|
|
625
|
+
charlieBranch.replace(2, "free-range eggs", "trusting");
|
|
626
|
+
|
|
627
|
+
// Merge all branches back to source
|
|
628
|
+
aliceBranch.core.mergeBranch();
|
|
629
|
+
bobBranch.core.mergeBranch();
|
|
630
|
+
charlieBranch.core.mergeBranch();
|
|
631
|
+
|
|
632
|
+
// Wait for sync
|
|
633
|
+
await groceryList.core.waitForSync();
|
|
634
|
+
|
|
635
|
+
// Source list should contain all modifications
|
|
636
|
+
const expected = ["organic milk", "whole wheat bread", "free-range eggs"];
|
|
637
|
+
expect(groceryList.toJSON()).toEqual(expected);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("should handle appendItems from multiple branches", async () => {
|
|
641
|
+
const client1 = setupTestNode({
|
|
642
|
+
connected: true,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const group = client1.node.createGroup();
|
|
646
|
+
group.addMember("everyone", "writer");
|
|
647
|
+
|
|
648
|
+
const groceryList = group.createList(["milk"]);
|
|
649
|
+
|
|
650
|
+
// Create branches for each client
|
|
651
|
+
const aliceBranch = expectList(
|
|
652
|
+
groceryList.core
|
|
653
|
+
.createBranch("alice-branch", group.id)
|
|
654
|
+
.getCurrentContent(),
|
|
655
|
+
);
|
|
656
|
+
const bobBranch = expectList(
|
|
657
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Both branches append multiple grocery items
|
|
661
|
+
aliceBranch.appendItems(["bread", "butter"], undefined, "trusting");
|
|
662
|
+
bobBranch.appendItems(["eggs", "cheese"], undefined, "trusting");
|
|
663
|
+
|
|
664
|
+
// Merge both branches back to source
|
|
665
|
+
aliceBranch.core.mergeBranch();
|
|
666
|
+
bobBranch.core.mergeBranch();
|
|
667
|
+
|
|
668
|
+
// Wait for sync
|
|
669
|
+
await groceryList.core.waitForSync();
|
|
670
|
+
|
|
671
|
+
// Source list should contain all items
|
|
672
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
673
|
+
[
|
|
674
|
+
"milk",
|
|
675
|
+
"eggs",
|
|
676
|
+
"cheese",
|
|
677
|
+
"bread",
|
|
678
|
+
"butter",
|
|
679
|
+
]
|
|
680
|
+
`);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("should handle complex operations from multiple branches", async () => {
|
|
684
|
+
const client1 = setupTestNode({
|
|
685
|
+
connected: true,
|
|
686
|
+
});
|
|
687
|
+
const client2 = setupTestNode({
|
|
688
|
+
connected: true,
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const group = client1.node.createGroup();
|
|
692
|
+
group.addMember("everyone", "writer");
|
|
693
|
+
|
|
694
|
+
const groceryList = group.createList(["milk", "bread", "eggs"]);
|
|
695
|
+
|
|
696
|
+
// Create branches for each client
|
|
697
|
+
const aliceBranch = expectList(
|
|
698
|
+
groceryList.core
|
|
699
|
+
.createBranch("alice-branch", group.id)
|
|
700
|
+
.getCurrentContent(),
|
|
701
|
+
);
|
|
702
|
+
const bobBranch = expectList(
|
|
703
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
// Alice performs multiple operations
|
|
707
|
+
aliceBranch.delete(1, "trusting"); // Remove "bread"
|
|
708
|
+
aliceBranch.append("cheese", undefined, "trusting");
|
|
709
|
+
aliceBranch.prepend("yogurt", undefined, "trusting");
|
|
710
|
+
|
|
711
|
+
// Bob performs different operations
|
|
712
|
+
bobBranch.replace(0, "organic milk", "trusting");
|
|
713
|
+
bobBranch.append("butter", undefined, "trusting");
|
|
714
|
+
bobBranch.delete(2, "trusting"); // Remove "eggs"
|
|
715
|
+
|
|
716
|
+
// Merge both branches back to source
|
|
717
|
+
aliceBranch.core.mergeBranch();
|
|
718
|
+
bobBranch.core.mergeBranch();
|
|
719
|
+
|
|
720
|
+
// Wait for sync
|
|
721
|
+
await groceryList.core.waitForSync();
|
|
722
|
+
|
|
723
|
+
// Source list should contain the combined result
|
|
724
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
725
|
+
[
|
|
726
|
+
"yogurt",
|
|
727
|
+
"organic milk",
|
|
728
|
+
"butter",
|
|
729
|
+
"cheese",
|
|
730
|
+
]
|
|
731
|
+
`);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("should handle multiple branch merges with incremental changes", async () => {
|
|
735
|
+
const client1 = setupTestNode({
|
|
736
|
+
connected: true,
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
const group = client1.node.createGroup();
|
|
740
|
+
group.addMember("everyone", "writer");
|
|
741
|
+
|
|
742
|
+
const groceryList = group.createList(["milk"]);
|
|
743
|
+
|
|
744
|
+
// Create branches for each client
|
|
745
|
+
const aliceBranch = expectList(
|
|
746
|
+
groceryList.core
|
|
747
|
+
.createBranch("alice-branch", group.id)
|
|
748
|
+
.getCurrentContent(),
|
|
749
|
+
);
|
|
750
|
+
const bobBranch = expectList(
|
|
751
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
752
|
+
);
|
|
753
|
+
const charlieBranch = expectList(
|
|
754
|
+
groceryList.core
|
|
755
|
+
.createBranch("charlie-branch", group.id)
|
|
756
|
+
.getCurrentContent(),
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
// Each branch adds grocery items
|
|
760
|
+
aliceBranch.append("bread", undefined, "trusting");
|
|
761
|
+
bobBranch.append("eggs", undefined, "trusting");
|
|
762
|
+
charlieBranch.append("cheese", undefined, "trusting");
|
|
763
|
+
|
|
764
|
+
// Merge branches one by one
|
|
765
|
+
aliceBranch.core.mergeBranch();
|
|
766
|
+
await groceryList.core.waitForSync();
|
|
767
|
+
expect(groceryList.toJSON()).toEqual(["milk", "bread"]);
|
|
768
|
+
|
|
769
|
+
bobBranch.core.mergeBranch();
|
|
770
|
+
await groceryList.core.waitForSync();
|
|
771
|
+
expect(groceryList.toJSON()).toEqual(["milk", "eggs", "bread"]);
|
|
772
|
+
|
|
773
|
+
charlieBranch.core.mergeBranch();
|
|
774
|
+
await groceryList.core.waitForSync();
|
|
775
|
+
expect(groceryList.toJSON()).toEqual(["milk", "cheese", "eggs", "bread"]);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test("should resolve conflicts when multiple branches modify the same position", async () => {
|
|
779
|
+
const client1 = setupTestNode({
|
|
780
|
+
connected: true,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
const group = client1.node.createGroup();
|
|
784
|
+
group.addMember("everyone", "writer");
|
|
785
|
+
|
|
786
|
+
const groceryList = group.createList(["milk", "bread", "eggs"]);
|
|
787
|
+
|
|
788
|
+
// Create branches for each client
|
|
789
|
+
const aliceBranch = expectList(
|
|
790
|
+
groceryList.core
|
|
791
|
+
.createBranch("alice-branch", group.id)
|
|
792
|
+
.getCurrentContent(),
|
|
793
|
+
);
|
|
794
|
+
const bobBranch = expectList(
|
|
795
|
+
groceryList.core.createBranch("bob-branch", group.id).getCurrentContent(),
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
// Both branches try to replace the same item at position 1
|
|
799
|
+
aliceBranch.replace(1, "whole wheat bread", "trusting");
|
|
800
|
+
bobBranch.replace(1, "sourdough bread", "trusting");
|
|
801
|
+
|
|
802
|
+
// Merge both branches back to source
|
|
803
|
+
aliceBranch.core.mergeBranch();
|
|
804
|
+
bobBranch.core.mergeBranch();
|
|
805
|
+
|
|
806
|
+
// Wait for sync
|
|
807
|
+
await groceryList.core.waitForSync();
|
|
808
|
+
|
|
809
|
+
// Source list should have a consistent state after conflict resolution
|
|
810
|
+
expect(groceryList.toJSON()).toContain("whole wheat bread");
|
|
811
|
+
expect(groceryList.toJSON()).toContain("sourdough bread");
|
|
812
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
813
|
+
[
|
|
814
|
+
"milk",
|
|
815
|
+
"sourdough bread",
|
|
816
|
+
"whole wheat bread",
|
|
817
|
+
"eggs",
|
|
818
|
+
]
|
|
819
|
+
`);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
test("should handle concurrent deletions at overlapping positions across branches", async () => {
|
|
823
|
+
const client1 = setupTestNode({
|
|
824
|
+
connected: true,
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
const group = client1.node.createGroup();
|
|
828
|
+
group.addMember("everyone", "writer");
|
|
829
|
+
|
|
830
|
+
const list = group.createList(["milk", "bread", "eggs"]);
|
|
831
|
+
|
|
832
|
+
// Create branches for each client
|
|
833
|
+
const mondayShoppingBranch = expectList(
|
|
834
|
+
list.core.createBranch("monday-shopping", group.id).getCurrentContent(),
|
|
835
|
+
);
|
|
836
|
+
const tuesdayShoppingBranch = expectList(
|
|
837
|
+
list.core.createBranch("tuesday-shopping", group.id).getCurrentContent(),
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
mondayShoppingBranch.delete(list.asArray().indexOf("milk"), "trusting");
|
|
841
|
+
tuesdayShoppingBranch.delete(list.asArray().indexOf("eggs"), "trusting");
|
|
842
|
+
|
|
843
|
+
// Merge both branches back to source
|
|
844
|
+
mondayShoppingBranch.core.mergeBranch();
|
|
845
|
+
tuesdayShoppingBranch.core.mergeBranch();
|
|
846
|
+
|
|
847
|
+
// Source list should reflect all deletions
|
|
848
|
+
expect(list.toJSON()).toEqual(["bread"]);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("should handle branching from different sessions and merging back", async () => {
|
|
852
|
+
const client1 = setupTestNode({
|
|
853
|
+
connected: true,
|
|
854
|
+
});
|
|
855
|
+
const client2 = setupTestNode({
|
|
856
|
+
connected: true,
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const group = client1.node.createGroup();
|
|
860
|
+
group.addMember("everyone", "writer");
|
|
861
|
+
|
|
862
|
+
const groceryList = group.createList(["milk", "bread"]);
|
|
863
|
+
|
|
864
|
+
// Client1 creates a branch
|
|
865
|
+
const aliceBranch = expectList(
|
|
866
|
+
groceryList.core
|
|
867
|
+
.createBranch("alice-shopping-branch", group.id)
|
|
868
|
+
.getCurrentContent(),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
// Client1 adds items to the branch
|
|
872
|
+
aliceBranch.append("eggs", undefined, "trusting");
|
|
873
|
+
|
|
874
|
+
// Client2 loads the branch from a different session
|
|
875
|
+
const branchOnClient2 = await loadCoValueOrFail(
|
|
876
|
+
client2.node,
|
|
877
|
+
aliceBranch.id,
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
// Client2 adds more items and removes some existing ones
|
|
881
|
+
branchOnClient2.append("cheese", undefined, "trusting");
|
|
882
|
+
branchOnClient2.delete(
|
|
883
|
+
branchOnClient2.asArray().indexOf("bread"),
|
|
884
|
+
"trusting",
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
// Merge the branch back to source
|
|
888
|
+
branchOnClient2.core.mergeBranch();
|
|
889
|
+
|
|
890
|
+
// Wait for sync
|
|
891
|
+
await groceryList.core.waitForSync();
|
|
892
|
+
|
|
893
|
+
// Source list should contain the final state
|
|
894
|
+
expect(groceryList.toJSON()).toEqual(["milk", "eggs", "cheese"]);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("should handle multiple branches from different sessions with complex operations", async () => {
|
|
898
|
+
const client1 = setupTestNode({
|
|
899
|
+
connected: true,
|
|
900
|
+
});
|
|
901
|
+
const client2 = setupTestNode({
|
|
902
|
+
connected: true,
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const group = client1.node.createGroup();
|
|
906
|
+
group.addMember("everyone", "writer");
|
|
907
|
+
|
|
908
|
+
const groceryList = group.createList(["milk"]);
|
|
909
|
+
|
|
910
|
+
// Client1 creates first branch
|
|
911
|
+
const aliceBranch = expectList(
|
|
912
|
+
groceryList.core
|
|
913
|
+
.createBranch("alice-shopping-branch", group.id)
|
|
914
|
+
.getCurrentContent(),
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
// Client1 adds items to first branch
|
|
918
|
+
aliceBranch.append("bread", undefined, "trusting");
|
|
919
|
+
|
|
920
|
+
// Client2 creates second branch
|
|
921
|
+
const bobBranch = expectList(
|
|
922
|
+
groceryList.core
|
|
923
|
+
.createBranch("bob-shopping-branch", group.id)
|
|
924
|
+
.getCurrentContent(),
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
// Client2 adds different items to second branch
|
|
928
|
+
bobBranch.append("eggs", undefined, "trusting");
|
|
929
|
+
|
|
930
|
+
// Client2 loads first branch and modifies it
|
|
931
|
+
const aliceBranchOnClient2 = await loadCoValueOrFail(
|
|
932
|
+
client2.node,
|
|
933
|
+
aliceBranch.id,
|
|
934
|
+
);
|
|
935
|
+
aliceBranchOnClient2.append("butter", undefined, "trusting");
|
|
936
|
+
|
|
937
|
+
// Merge all branches back to source
|
|
938
|
+
aliceBranchOnClient2.core.mergeBranch();
|
|
939
|
+
|
|
940
|
+
// Make the second merge happen later than the previous one, so the ordering is not based on the random sessionIDs
|
|
941
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
942
|
+
|
|
943
|
+
bobBranch.core.mergeBranch();
|
|
944
|
+
|
|
945
|
+
// Wait for sync
|
|
946
|
+
await groceryList.core.waitForSync();
|
|
947
|
+
|
|
948
|
+
// Source list should contain all changes
|
|
949
|
+
expect(groceryList.toJSON()).toMatchInlineSnapshot(`
|
|
950
|
+
[
|
|
951
|
+
"milk",
|
|
952
|
+
"eggs",
|
|
953
|
+
"bread",
|
|
954
|
+
"butter",
|
|
955
|
+
]
|
|
956
|
+
`);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
@@ -507,9 +507,9 @@ describe("loading coValues from server", () => {
|
|
|
507
507
|
connected: true,
|
|
508
508
|
});
|
|
509
509
|
|
|
510
|
-
await loadCoValueOrFail(client.node, largeMap.id);
|
|
510
|
+
const mapOnClient = await loadCoValueOrFail(client.node, largeMap.id);
|
|
511
511
|
|
|
512
|
-
await
|
|
512
|
+
await mapOnClient.core.waitForFullStreaming();
|
|
513
513
|
|
|
514
514
|
expect(
|
|
515
515
|
SyncMessagesLog.getMessages({
|
|
@@ -14,12 +14,15 @@ import { getDbPath } from "./testStorage";
|
|
|
14
14
|
// We want to simulate a real world communication that happens asynchronously
|
|
15
15
|
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
16
16
|
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
setMaxRecommendedTxSize(100 * 1024);
|
|
19
|
+
});
|
|
20
|
+
|
|
17
21
|
describe("client with storage syncs with server", () => {
|
|
18
22
|
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
19
23
|
|
|
20
24
|
beforeEach(async () => {
|
|
21
25
|
vi.resetAllMocks();
|
|
22
|
-
setMaxRecommendedTxSize(100 * 1024);
|
|
23
26
|
SyncMessagesLog.clear();
|
|
24
27
|
jazzCloud = setupTestNode({
|
|
25
28
|
isSyncServer: true,
|
|
@@ -434,6 +437,8 @@ describe("client syncs with a server with storage", () => {
|
|
|
434
437
|
});
|
|
435
438
|
|
|
436
439
|
test("large coValue streaming from cold server", async () => {
|
|
440
|
+
setMaxRecommendedTxSize(1000);
|
|
441
|
+
|
|
437
442
|
const server = setupTestNode({
|
|
438
443
|
isSyncServer: true,
|
|
439
444
|
});
|
|
@@ -470,8 +475,6 @@ describe("client syncs with a server with storage", () => {
|
|
|
470
475
|
|
|
471
476
|
await largeMap.core.waitForSync();
|
|
472
477
|
|
|
473
|
-
SyncMessagesLog.clear();
|
|
474
|
-
|
|
475
478
|
server.restart();
|
|
476
479
|
|
|
477
480
|
server.addStorage({
|
|
@@ -491,9 +494,10 @@ describe("client syncs with a server with storage", () => {
|
|
|
491
494
|
storage,
|
|
492
495
|
});
|
|
493
496
|
|
|
494
|
-
|
|
497
|
+
SyncMessagesLog.clear();
|
|
495
498
|
|
|
496
|
-
await
|
|
499
|
+
const mapOnClient2 = await loadCoValueOrFail(client.node, largeMap.id);
|
|
500
|
+
await mapOnClient2.core.waitForFullStreaming();
|
|
497
501
|
|
|
498
502
|
expect(
|
|
499
503
|
SyncMessagesLog.getMessages({
|
|
@@ -516,13 +520,6 @@ describe("client syncs with a server with storage", () => {
|
|
|
516
520
|
"storage -> client | CONTENT Map header: true new: After: 7 New: 1",
|
|
517
521
|
"storage -> client | CONTENT Map header: true new: After: 8 New: 1",
|
|
518
522
|
"storage -> client | CONTENT Map header: true new: After: 9 New: 1",
|
|
519
|
-
"storage -> client | CONTENT Map header: true new: After: 10 New: 0",
|
|
520
|
-
"server -> storage | LOAD Group sessions: empty",
|
|
521
|
-
"storage -> server | CONTENT Group header: true new: After: 0 New: 5",
|
|
522
|
-
"server -> client | KNOWN Group sessions: header/5",
|
|
523
|
-
"server -> storage | LOAD Map sessions: empty",
|
|
524
|
-
"storage -> server | CONTENT Map header: true new: After: 0 New: 1 expectContentUntil: header/10",
|
|
525
|
-
"server -> client | KNOWN Map sessions: header/10",
|
|
526
523
|
]
|
|
527
524
|
`);
|
|
528
525
|
});
|