jazz-tools 0.13.16 → 0.13.18

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 (83) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +19 -0
  3. package/dist/{chunk-GIZWJXSR.js → chunk-ITSHLDQB.js} +731 -600
  4. package/dist/chunk-ITSHLDQB.js.map +1 -0
  5. package/dist/coValues/account.d.ts +5 -4
  6. package/dist/coValues/account.d.ts.map +1 -1
  7. package/dist/coValues/coFeed.d.ts +3 -3
  8. package/dist/coValues/coFeed.d.ts.map +1 -1
  9. package/dist/coValues/coList.d.ts +1 -0
  10. package/dist/coValues/coList.d.ts.map +1 -1
  11. package/dist/coValues/coMap.d.ts +4 -2
  12. package/dist/coValues/coMap.d.ts.map +1 -1
  13. package/dist/coValues/coPlainText.d.ts +2 -2
  14. package/dist/coValues/coPlainText.d.ts.map +1 -1
  15. package/dist/coValues/deepLoading.d.ts +1 -9
  16. package/dist/coValues/deepLoading.d.ts.map +1 -1
  17. package/dist/coValues/extensions/imageDef.d.ts.map +1 -1
  18. package/dist/coValues/group.d.ts.map +1 -1
  19. package/dist/coValues/inbox.d.ts.map +1 -1
  20. package/dist/coValues/interfaces.d.ts +4 -1
  21. package/dist/coValues/interfaces.d.ts.map +1 -1
  22. package/dist/implementation/createContext.d.ts.map +1 -1
  23. package/dist/implementation/refs.d.ts +5 -10
  24. package/dist/implementation/refs.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/internal.d.ts +1 -1
  27. package/dist/internal.d.ts.map +1 -1
  28. package/dist/subscribe/CoValueCoreSubscription.d.ts +14 -0
  29. package/dist/subscribe/CoValueCoreSubscription.d.ts.map +1 -0
  30. package/dist/subscribe/JazzError.d.ts +16 -0
  31. package/dist/subscribe/JazzError.d.ts.map +1 -0
  32. package/dist/subscribe/SubscriptionScope.d.ts +42 -0
  33. package/dist/subscribe/SubscriptionScope.d.ts.map +1 -0
  34. package/dist/subscribe/index.d.ts +21 -0
  35. package/dist/subscribe/index.d.ts.map +1 -0
  36. package/dist/subscribe/types.d.ts +12 -0
  37. package/dist/subscribe/types.d.ts.map +1 -0
  38. package/dist/subscribe/utils.d.ts +10 -0
  39. package/dist/subscribe/utils.d.ts.map +1 -0
  40. package/dist/testing.js +2 -2
  41. package/dist/testing.js.map +1 -1
  42. package/dist/tests/coMap.record.test.d.ts +2 -0
  43. package/dist/tests/coMap.record.test.d.ts.map +1 -0
  44. package/dist/tests/utils.d.ts +2 -2
  45. package/dist/tests/utils.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/coValues/account.ts +43 -31
  48. package/src/coValues/coFeed.ts +28 -13
  49. package/src/coValues/coList.ts +13 -17
  50. package/src/coValues/coMap.ts +72 -80
  51. package/src/coValues/coPlainText.ts +13 -2
  52. package/src/coValues/deepLoading.ts +4 -277
  53. package/src/coValues/extensions/imageDef.ts +1 -7
  54. package/src/coValues/group.ts +7 -6
  55. package/src/coValues/inbox.ts +4 -11
  56. package/src/coValues/interfaces.ts +54 -111
  57. package/src/implementation/createContext.ts +3 -4
  58. package/src/implementation/invites.ts +2 -2
  59. package/src/implementation/refs.ts +30 -121
  60. package/src/internal.ts +1 -2
  61. package/src/subscribe/CoValueCoreSubscription.ts +71 -0
  62. package/src/subscribe/JazzError.ts +48 -0
  63. package/src/subscribe/SubscriptionScope.ts +523 -0
  64. package/src/subscribe/index.ts +82 -0
  65. package/src/subscribe/types.ts +7 -0
  66. package/src/subscribe/utils.ts +36 -0
  67. package/src/testing.ts +1 -1
  68. package/src/tests/ContextManager.test.ts +13 -9
  69. package/src/tests/coFeed.test.ts +104 -4
  70. package/src/tests/coList.test.ts +304 -115
  71. package/src/tests/coMap.record.test.ts +325 -0
  72. package/src/tests/coMap.test.ts +718 -645
  73. package/src/tests/coPlainText.test.ts +2 -2
  74. package/src/tests/createContext.test.ts +8 -8
  75. package/src/tests/deepLoading.test.ts +8 -34
  76. package/src/tests/groupsAndAccounts.test.ts +6 -4
  77. package/src/tests/subscribe.test.ts +357 -42
  78. package/src/tests/utils.ts +8 -6
  79. package/tsconfig.json +2 -1
  80. package/dist/chunk-GIZWJXSR.js.map +0 -1
  81. package/dist/implementation/subscriptionScope.d.ts +0 -34
  82. package/dist/implementation/subscriptionScope.d.ts.map +0 -1
  83. package/src/implementation/subscriptionScope.ts +0 -165
@@ -1,27 +1,40 @@
1
1
  import { cojsonInternals } from "cojson";
2
2
  import { WasmCrypto } from "cojson/crypto/WasmCrypto";
3
- import { describe, expect, test } from "vitest";
3
+ import { beforeEach, describe, expect, test, vi } from "vitest";
4
4
  import {
5
5
  Account,
6
6
  CoList,
7
7
  CoMap,
8
8
  Group,
9
+ Resolved,
9
10
  co,
10
11
  createJazzContextFromExistingCredentials,
11
12
  isControlledAccount,
13
+ subscribeToCoValue,
12
14
  } from "../index.js";
13
15
  import { randomSessionProvider } from "../internal.js";
16
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
17
+ import { waitFor } from "./utils.js";
14
18
 
15
19
  const connectedPeers = cojsonInternals.connectedPeers;
16
20
 
17
21
  const Crypto = await WasmCrypto.create();
18
22
 
19
- describe("Simple CoList operations", async () => {
20
- const me = await Account.create({
23
+ let me = await Account.create({
24
+ creationProps: { name: "Hermes Puggington" },
25
+ crypto: Crypto,
26
+ });
27
+
28
+ beforeEach(async () => {
29
+ await setupJazzTestSync();
30
+
31
+ me = await createJazzTestAccount({
32
+ isCurrentActiveAccount: true,
21
33
  creationProps: { name: "Hermes Puggington" },
22
- crypto: Crypto,
23
34
  });
35
+ });
24
36
 
37
+ describe("Simple CoList operations", async () => {
25
38
  class TestList extends CoList.Of(co.string) {}
26
39
 
27
40
  const list = TestList.create(["bread", "butter", "onion"], { owner: me });
@@ -347,11 +360,6 @@ describe("Simple CoList operations", async () => {
347
360
  });
348
361
 
349
362
  describe("CoList applyDiff operations", async () => {
350
- const me = await Account.create({
351
- creationProps: { name: "Hermes Puggington" },
352
- crypto: Crypto,
353
- });
354
-
355
363
  test("applyDiff with primitive values", () => {
356
364
  class StringList extends CoList.Of(co.string) {}
357
365
  const list = StringList.create(["a", "b", "c"], { owner: me });
@@ -485,147 +493,328 @@ describe("CoList resolution", async () => {
485
493
  expect(list[0]?.[0]?.id).toBeDefined();
486
494
  expect(list[1]?.[0]?.[0]).toBe("c");
487
495
  });
496
+ });
488
497
 
489
- test("Loading and availability", async () => {
490
- const { me, list } = await initNodeAndList();
491
-
492
- const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
493
- peer1role: "server",
494
- peer2role: "client",
495
- });
496
- if (!isControlledAccount(me)) {
497
- throw "me is not a controlled account";
498
+ describe("CoList subscription", async () => {
499
+ test("subscription on a locally available list with deep resolve", async () => {
500
+ class Item extends CoMap {
501
+ name = co.string;
498
502
  }
499
- me._raw.core.node.syncManager.addPeer(secondPeer);
500
- const { account: meOnSecondPeer } =
501
- await createJazzContextFromExistingCredentials({
502
- credentials: {
503
- accountID: me.id,
504
- secret: me._raw.agentSecret,
503
+
504
+ class TestList extends CoList.Of(co.ref(Item)) {}
505
+
506
+ const list = TestList.create(
507
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
508
+ { owner: me },
509
+ );
510
+
511
+ const updates: Resolved<TestList, { $each: true }>[] = [];
512
+ const spy = vi.fn((list) => updates.push(list));
513
+
514
+ TestList.subscribe(
515
+ list.id,
516
+ {
517
+ resolve: {
518
+ $each: true,
505
519
  },
506
- sessionProvider: randomSessionProvider,
507
- peersToLoadFrom: [initialAsPeer],
508
- crypto: Crypto,
509
- });
520
+ },
521
+ spy,
522
+ );
510
523
 
511
- const loadedList = await TestList.load(list.id, { loadAs: meOnSecondPeer });
524
+ expect(spy).not.toHaveBeenCalled();
512
525
 
513
- expect(loadedList?.[0]).toBe(null);
514
- expect(loadedList?._refs[0]?.id).toEqual(list[0]!.id);
526
+ await waitFor(() => expect(spy).toHaveBeenCalled());
515
527
 
516
- const loadedNestedList = await NestedList.load(list[0]!.id, {
517
- loadAs: meOnSecondPeer,
518
- });
528
+ expect(spy).toHaveBeenCalledTimes(1);
529
+
530
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
531
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
532
+
533
+ list[0]!.name = "Updated Item 1";
519
534
 
520
- expect(loadedList?.[0]).toBeDefined();
521
- expect(loadedList?.[0]?.[0]).toBe(null);
522
- expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]![0]!.id);
523
- // TODO: this should be ref equal
524
- // expect(loadedList?._refs[0]?.value).toEqual(loadedNestedList);
525
- expect(loadedList?._refs[0]?.value?.toJSON()).toEqual(
526
- loadedNestedList?.toJSON(),
535
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
536
+
537
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
538
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
539
+
540
+ expect(spy).toHaveBeenCalledTimes(2);
541
+ });
542
+
543
+ test("subscription on a locally available list with autoload", async () => {
544
+ class Item extends CoMap {
545
+ name = co.string;
546
+ }
547
+
548
+ class TestList extends CoList.Of(co.ref(Item)) {}
549
+
550
+ const list = TestList.create(
551
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
552
+ { owner: me },
527
553
  );
528
554
 
529
- const loadedTwiceNestedList = await TwiceNestedList.load(list[0]![0]!.id, {
530
- loadAs: meOnSecondPeer,
531
- });
555
+ const updates: TestList[] = [];
556
+ const spy = vi.fn((list) => updates.push(list));
557
+
558
+ TestList.subscribe(list.id, {}, spy);
559
+
560
+ expect(spy).not.toHaveBeenCalled();
561
+
562
+ await waitFor(() => expect(spy).toHaveBeenCalled());
563
+
564
+ expect(spy).toHaveBeenCalledTimes(1);
565
+
566
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
567
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
568
+
569
+ list[0]!.name = "Updated Item 1";
570
+
571
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
572
+
573
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
574
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
532
575
 
533
- expect(loadedList?.[0]?.[0]).toBeDefined();
534
- expect(loadedList?.[0]?.[0]?.[0]).toBe("a");
535
- expect(loadedList?.[0]?.[0]?.joined()).toBe("a,b");
536
- expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]?.[0]?.id);
537
- // TODO: this should be ref equal
538
- // expect(loadedList?.[0]?._refs[0]?.value).toEqual(loadedTwiceNestedList);
539
- expect(loadedList?.[0]?._refs[0]?.value?.toJSON()).toEqual(
540
- loadedTwiceNestedList?.toJSON(),
576
+ expect(spy).toHaveBeenCalledTimes(2);
577
+ });
578
+
579
+ test("subscription on a locally available list with syncResolution", async () => {
580
+ class Item extends CoMap {
581
+ name = co.string;
582
+ }
583
+
584
+ class TestList extends CoList.Of(co.ref(Item)) {}
585
+
586
+ const list = TestList.create(
587
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
588
+ { owner: me },
541
589
  );
542
590
 
543
- const otherNestedList = NestedList.create(
544
- [TwiceNestedList.create(["e", "f"], { owner: meOnSecondPeer })],
545
- { owner: meOnSecondPeer },
591
+ const updates: TestList[] = [];
592
+ const spy = vi.fn((list) => updates.push(list));
593
+
594
+ subscribeToCoValue(
595
+ TestList,
596
+ list.id,
597
+ {
598
+ syncResolution: true,
599
+ loadAs: Account.getMe(),
600
+ },
601
+ spy,
546
602
  );
547
603
 
548
- loadedList![0] = otherNestedList;
549
- // TODO: this should be ref equal
550
- // expect(loadedList?.[0]).toEqual(otherNestedList);
551
- expect(loadedList?._refs[0]?.value?.toJSON()).toEqual(
552
- otherNestedList.toJSON(),
604
+ expect(spy).toHaveBeenCalled();
605
+ expect(spy).toHaveBeenCalledTimes(1);
606
+
607
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
608
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
609
+
610
+ expect(spy).toHaveBeenCalledTimes(1);
611
+
612
+ list[0]!.name = "Updated Item 1";
613
+
614
+ expect(spy).toHaveBeenCalledTimes(2);
615
+
616
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
617
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
618
+
619
+ expect(spy).toHaveBeenCalledTimes(2);
620
+ });
621
+
622
+ test("subscription on a remotely available list with deep resolve", async () => {
623
+ class Item extends CoMap {
624
+ name = co.string;
625
+ }
626
+
627
+ class TestList extends CoList.Of(co.ref(Item)) {}
628
+
629
+ const group = Group.create();
630
+ group.addMember("everyone", "writer");
631
+
632
+ const list = TestList.create(
633
+ [
634
+ Item.create({ name: "Item 1" }, group),
635
+ Item.create({ name: "Item 2" }, group),
636
+ ],
637
+ group,
553
638
  );
554
- expect(loadedList?._refs[0]?.id).toEqual(otherNestedList.id);
639
+
640
+ const userB = await createJazzTestAccount();
641
+
642
+ const updates: Resolved<TestList, { $each: true }>[] = [];
643
+ const spy = vi.fn((list) => updates.push(list));
644
+
645
+ TestList.subscribe(
646
+ list.id,
647
+ {
648
+ resolve: {
649
+ $each: true,
650
+ },
651
+ loadAs: userB,
652
+ },
653
+ spy,
654
+ );
655
+
656
+ expect(spy).not.toHaveBeenCalled();
657
+
658
+ await waitFor(() => expect(spy).toHaveBeenCalled());
659
+
660
+ expect(spy).toHaveBeenCalledTimes(1);
661
+
662
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
663
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
664
+
665
+ list[0]!.name = "Updated Item 1";
666
+
667
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
668
+
669
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
670
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
671
+
672
+ expect(spy).toHaveBeenCalledTimes(2);
555
673
  });
556
674
 
557
- test("Subscription & auto-resolution", async () => {
558
- const { me, list } = await initNodeAndList();
675
+ test("subscription on a remotely available list with autoload", async () => {
676
+ class Item extends CoMap {
677
+ name = co.string;
678
+ }
559
679
 
560
- const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
561
- peer1role: "server",
562
- peer2role: "client",
563
- });
564
- if (!isControlledAccount(me)) {
565
- throw "me is not a controlled account";
680
+ class TestList extends CoList.Of(co.ref(Item)) {}
681
+
682
+ const group = Group.create();
683
+ group.addMember("everyone", "writer");
684
+
685
+ const list = TestList.create(
686
+ [
687
+ Item.create({ name: "Item 1" }, group),
688
+ Item.create({ name: "Item 2" }, group),
689
+ ],
690
+ group,
691
+ );
692
+
693
+ const updates: TestList[] = [];
694
+ const spy = vi.fn((list) => updates.push(list));
695
+
696
+ const userB = await createJazzTestAccount();
697
+
698
+ TestList.subscribe(
699
+ list.id,
700
+ {
701
+ loadAs: userB,
702
+ },
703
+ spy,
704
+ );
705
+
706
+ expect(spy).not.toHaveBeenCalled();
707
+
708
+ await waitFor(() => expect(spy).toHaveBeenCalled());
709
+
710
+ expect(spy).toHaveBeenCalledTimes(1);
711
+
712
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
713
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
714
+
715
+ list[0]!.name = "Updated Item 1";
716
+
717
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
718
+
719
+ expect(updates[1]?.[0]?.name).toEqual("Updated Item 1");
720
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
721
+
722
+ expect(spy).toHaveBeenCalledTimes(2);
723
+ });
724
+
725
+ test("replacing list items triggers updates", async () => {
726
+ class Item extends CoMap {
727
+ name = co.string;
566
728
  }
567
- me._raw.core.node.syncManager.addPeer(secondPeer);
568
- const { account: meOnSecondPeer } =
569
- await createJazzContextFromExistingCredentials({
570
- credentials: {
571
- accountID: me.id,
572
- secret: me._raw.agentSecret,
573
- },
574
- sessionProvider: randomSessionProvider,
575
- peersToLoadFrom: [initialAsPeer],
576
- crypto: Crypto,
577
- });
578
729
 
579
- const queue = new cojsonInternals.Channel();
730
+ class TestList extends CoList.Of(co.ref(Item)) {}
731
+
732
+ const list = TestList.create(
733
+ [Item.create({ name: "Item 1" }), Item.create({ name: "Item 2" })],
734
+ { owner: me },
735
+ );
736
+
737
+ const updates: Resolved<TestList, { $each: true }>[] = [];
738
+ const spy = vi.fn((list) => updates.push(list));
580
739
 
581
740
  TestList.subscribe(
582
741
  list.id,
583
- { loadAs: meOnSecondPeer },
584
- (subscribedList) => {
585
- console.log(
586
- "subscribedList?.[0]?.[0]?.[0]",
587
- subscribedList?.[0]?.[0]?.[0],
588
- );
589
- void queue.push(subscribedList);
742
+ {
743
+ resolve: {
744
+ $each: true,
745
+ },
590
746
  },
747
+ spy,
591
748
  );
592
749
 
593
- const update1 = (await queue.next()).value;
594
- expect(update1?.[0]).toBe(null);
750
+ expect(spy).not.toHaveBeenCalled();
595
751
 
596
- const update2 = (await queue.next()).value;
597
- expect(update2?.[0]).toBeDefined();
598
- expect(update2?.[0]?.[0]).toBe(null);
752
+ await waitFor(() => expect(spy).toHaveBeenCalled());
599
753
 
600
- const update3 = (await queue.next()).value;
601
- expect(update3?.[0]?.[0]).toBeDefined();
602
- expect(update3?.[0]?.[0]?.[0]).toBe("a");
603
- expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
754
+ expect(spy).toHaveBeenCalledTimes(1);
604
755
 
605
- update3[0]![0]![0] = "x";
756
+ expect(updates[0]?.[0]?.name).toEqual("Item 1");
757
+ expect(updates[0]?.[1]?.name).toEqual("Item 2");
606
758
 
607
- const update4 = (await queue.next()).value;
608
- expect(update4?.[0]?.[0]?.[0]).toBe("x");
759
+ list[0] = Item.create({ name: "New Item 1" });
609
760
 
610
- // When assigning a new nested value, we get an update
761
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
611
762
 
612
- const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
613
- owner: meOnSecondPeer,
614
- });
763
+ expect(updates[1]?.[0]?.name).toEqual("New Item 1");
764
+ expect(updates[1]?.[1]?.name).toEqual("Item 2");
615
765
 
616
- const newNestedList = NestedList.create([newTwiceNestedList], {
617
- owner: meOnSecondPeer,
618
- });
766
+ expect(spy).toHaveBeenCalledTimes(2);
767
+ });
768
+
769
+ test("pushing a new item triggers updates correctly", async () => {
770
+ class Item extends CoMap {
771
+ name = co.string;
772
+ }
773
+
774
+ class TestList extends CoList.Of(co.ref(Item)) {}
775
+
776
+ const group = Group.create();
777
+ group.addMember("everyone", "writer");
778
+
779
+ const list = TestList.create(
780
+ [
781
+ Item.create({ name: "Item 1" }, group),
782
+ Item.create({ name: "Item 2" }, group),
783
+ ],
784
+ group,
785
+ );
786
+
787
+ const updates: TestList[] = [];
788
+ const spy = vi.fn((list) => updates.push(list));
789
+
790
+ const userB = await createJazzTestAccount();
791
+
792
+ TestList.subscribe(
793
+ list.id,
794
+ {
795
+ loadAs: userB,
796
+ resolve: {
797
+ $each: true,
798
+ },
799
+ },
800
+ (update) => {
801
+ spy(update);
802
+
803
+ // The update should be triggered only when the new item is loaded
804
+ for (const item of update) {
805
+ expect(item).toBeDefined();
806
+ }
807
+ },
808
+ );
809
+
810
+ await waitFor(() => expect(spy).toHaveBeenCalled());
811
+
812
+ expect(spy).toHaveBeenCalledTimes(1);
619
813
 
620
- update4[0] = newNestedList;
814
+ list.push(Item.create({ name: "Item 3" }, group));
621
815
 
622
- const update5 = (await queue.next()).value;
623
- expect(update5?.[0]?.[0]?.[0]).toBe("y");
624
- expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
816
+ await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
625
817
 
626
- // we get updates when the new nested value changes
627
- newTwiceNestedList[0] = "w";
628
- const update6 = (await queue.next()).value;
629
- expect(update6?.[0]?.[0]?.[0]).toBe("w");
818
+ expect(spy).toHaveBeenCalledTimes(2);
630
819
  });
631
820
  });