cojson 0.18.5 → 0.18.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/coValueContentMessage.d.ts +2 -0
  4. package/dist/coValueContentMessage.d.ts.map +1 -1
  5. package/dist/coValueContentMessage.js +7 -0
  6. package/dist/coValueContentMessage.js.map +1 -1
  7. package/dist/coValueCore/SessionMap.d.ts +2 -2
  8. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  9. package/dist/coValueCore/SessionMap.js +2 -4
  10. package/dist/coValueCore/SessionMap.js.map +1 -1
  11. package/dist/coValueCore/branching.d.ts +35 -13
  12. package/dist/coValueCore/branching.d.ts.map +1 -1
  13. package/dist/coValueCore/branching.js +55 -37
  14. package/dist/coValueCore/branching.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +15 -7
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +126 -35
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +4 -2
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +6 -4
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coList.d.ts +8 -2
  24. package/dist/coValues/coList.d.ts.map +1 -1
  25. package/dist/coValues/coList.js +95 -58
  26. package/dist/coValues/coList.js.map +1 -1
  27. package/dist/coValues/coMap.d.ts +2 -2
  28. package/dist/coValues/coMap.d.ts.map +1 -1
  29. package/dist/coValues/coMap.js +8 -8
  30. package/dist/coValues/coMap.js.map +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +14 -1
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +6 -0
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +8 -0
  37. package/dist/config.js.map +1 -1
  38. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  39. package/dist/crypto/PureJSCrypto.js +14 -6
  40. package/dist/crypto/PureJSCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +5 -4
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +3 -3
  44. package/dist/exports.js.map +1 -1
  45. package/dist/ids.d.ts +1 -0
  46. package/dist/ids.d.ts.map +1 -1
  47. package/dist/ids.js.map +1 -1
  48. package/dist/localNode.d.ts.map +1 -1
  49. package/dist/localNode.js +7 -2
  50. package/dist/localNode.js.map +1 -1
  51. package/dist/storage/storageAsync.d.ts.map +1 -1
  52. package/dist/storage/storageAsync.js +7 -4
  53. package/dist/storage/storageAsync.js.map +1 -1
  54. package/dist/storage/storageSync.d.ts.map +1 -1
  55. package/dist/storage/storageSync.js +7 -4
  56. package/dist/storage/storageSync.js.map +1 -1
  57. package/dist/sync.d.ts +1 -3
  58. package/dist/sync.d.ts.map +1 -1
  59. package/dist/sync.js +8 -18
  60. package/dist/sync.js.map +1 -1
  61. package/dist/tests/branching.test.js +166 -4
  62. package/dist/tests/branching.test.js.map +1 -1
  63. package/dist/tests/coList.test.js +367 -1
  64. package/dist/tests/coList.test.js.map +1 -1
  65. package/dist/tests/coValueCore.test.js +45 -1
  66. package/dist/tests/coValueCore.test.js.map +1 -1
  67. package/dist/tests/messagesTestUtils.d.ts +1 -1
  68. package/dist/tests/sync.content.test.d.ts +2 -0
  69. package/dist/tests/sync.content.test.d.ts.map +1 -0
  70. package/dist/tests/sync.content.test.js +120 -0
  71. package/dist/tests/sync.content.test.js.map +1 -0
  72. package/dist/tests/sync.load.test.js +4 -4
  73. package/dist/tests/sync.load.test.js.map +1 -1
  74. package/dist/tests/sync.storage.test.js +1 -1
  75. package/dist/tests/sync.storageAsync.test.js +6 -10
  76. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  77. package/dist/tests/sync.upload.test.js +2 -2
  78. package/dist/tests/testUtils.d.ts +2 -2
  79. package/package.json +2 -2
  80. package/src/coValueContentMessage.ts +13 -0
  81. package/src/coValueCore/SessionMap.ts +2 -2
  82. package/src/coValueCore/branching.ts +105 -60
  83. package/src/coValueCore/coValueCore.ts +163 -41
  84. package/src/coValueCore/verifiedState.ts +8 -0
  85. package/src/coValues/coList.ts +129 -78
  86. package/src/coValues/coMap.ts +10 -12
  87. package/src/coValues/group.ts +14 -1
  88. package/src/config.ts +9 -0
  89. package/src/crypto/PureJSCrypto.ts +25 -13
  90. package/src/exports.ts +13 -2
  91. package/src/ids.ts +1 -0
  92. package/src/localNode.ts +8 -2
  93. package/src/storage/storageAsync.ts +8 -5
  94. package/src/storage/storageSync.ts +8 -4
  95. package/src/sync.ts +8 -32
  96. package/src/tests/branching.test.ts +255 -4
  97. package/src/tests/coList.test.ts +529 -1
  98. package/src/tests/coValueCore.test.ts +62 -2
  99. package/src/tests/sync.content.test.ts +153 -0
  100. package/src/tests/sync.load.test.ts +4 -4
  101. package/src/tests/sync.storage.test.ts +1 -1
  102. package/src/tests/sync.storageAsync.test.ts +9 -12
  103. package/src/tests/sync.upload.test.ts +2 -2
@@ -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
+ });
@@ -8,7 +8,6 @@ import {
8
8
  vi,
9
9
  } from "vitest";
10
10
  import { CoValueCore } from "../coValueCore/coValueCore.js";
11
- import { Transaction } from "../coValueCore/verifiedState.js";
12
11
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
13
12
  import { stableStringify } from "../jsonStringify.js";
14
13
  import { LocalNode } from "../localNode.js";
@@ -24,7 +23,7 @@ import {
24
23
  tearDownTestMetricReader,
25
24
  } from "./testUtils.js";
26
25
  import { CO_VALUE_PRIORITY } from "../priority.js";
27
- import { determineValidTransactions } from "../permissions.js";
26
+ import { setMaxTxSizeBytes } from "../config.js";
28
27
 
29
28
  const Crypto = await WasmCrypto.create();
30
29
 
@@ -57,6 +56,7 @@ test("transactions with wrong signature are rejected", () => {
57
56
  node.getCurrentAgent(),
58
57
  [{ hello: "world" }],
59
58
  undefined,
59
+ Date.now(),
60
60
  );
61
61
 
62
62
  transaction.madeAt = Date.now() + 1000;
@@ -87,6 +87,66 @@ test("transactions with wrong signature are rejected", () => {
87
87
  expect(newEntry.getValidSortedTransactions().length).toBe(0);
88
88
  });
89
89
 
90
+ describe("transactions that exceed the byte size limit are rejected", () => {
91
+ beforeEach(() => {
92
+ setMaxTxSizeBytes(1 * 1024);
93
+ });
94
+
95
+ afterEach(() => {
96
+ setMaxTxSizeBytes(1 * 1024 * 1024);
97
+ });
98
+
99
+ test("makeTransaction should throw error when transaction exceeds byte size limit", () => {
100
+ const [agent, sessionID] = randomAgentAndSessionID();
101
+ const node = new LocalNode(agent.agentSecret, sessionID, Crypto);
102
+
103
+ const coValue = node.createCoValue({
104
+ type: "costream",
105
+ ruleset: { type: "unsafeAllowAll" },
106
+ meta: null,
107
+ ...Crypto.createdNowUnique(),
108
+ });
109
+
110
+ const largeBinaryData = "x".repeat(1024 + 100);
111
+
112
+ expect(() => {
113
+ coValue.makeTransaction(
114
+ [
115
+ {
116
+ data: largeBinaryData,
117
+ },
118
+ ],
119
+ "trusting",
120
+ );
121
+ }).toThrow(/Transaction is too large to be synced/);
122
+ });
123
+
124
+ test("makeTransaction should work for transactions under byte size limit", () => {
125
+ const [agent, sessionID] = randomAgentAndSessionID();
126
+ const node = new LocalNode(agent.agentSecret, sessionID, Crypto);
127
+
128
+ const coValue = node.createCoValue({
129
+ type: "costream",
130
+ ruleset: { type: "unsafeAllowAll" },
131
+ meta: null,
132
+ ...Crypto.createdNowUnique(),
133
+ });
134
+
135
+ const smallData = "Hello, world!";
136
+
137
+ const success = coValue.makeTransaction(
138
+ [
139
+ {
140
+ data: smallData,
141
+ },
142
+ ],
143
+ "trusting",
144
+ );
145
+
146
+ expect(success).toBe(true);
147
+ });
148
+ });
149
+
90
150
  test("New transactions in a group correctly update owned values, including subscriptions", async () => {
91
151
  const [agent, sessionID] = randomAgentAndSessionID();
92
152
  const node = new LocalNode(agent.agentSecret, sessionID, Crypto);