cojson 0.18.29 → 0.18.31

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 (144) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/dist/PeerState.d.ts +23 -14
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +74 -23
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts +3 -3
  8. package/dist/SyncStateManager.d.ts.map +1 -1
  9. package/dist/SyncStateManager.js +18 -44
  10. package/dist/SyncStateManager.js.map +1 -1
  11. package/dist/coValueContentMessage.d.ts.map +1 -1
  12. package/dist/coValueContentMessage.js +2 -1
  13. package/dist/coValueContentMessage.js.map +1 -1
  14. package/dist/coValueCore/PeerKnownState.d.ts +21 -0
  15. package/dist/coValueCore/PeerKnownState.d.ts.map +1 -0
  16. package/dist/coValueCore/PeerKnownState.js +52 -0
  17. package/dist/coValueCore/PeerKnownState.js.map +1 -0
  18. package/dist/coValueCore/coValueCore.d.ts +39 -8
  19. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  20. package/dist/coValueCore/coValueCore.js +139 -40
  21. package/dist/coValueCore/coValueCore.js.map +1 -1
  22. package/dist/coValueCore/decryptTransactionChangesAndMeta.d.ts.map +1 -1
  23. package/dist/coValueCore/decryptTransactionChangesAndMeta.js +0 -5
  24. package/dist/coValueCore/decryptTransactionChangesAndMeta.js.map +1 -1
  25. package/dist/coValueCore/verifiedState.d.ts +0 -14
  26. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  27. package/dist/coValueCore/verifiedState.js +2 -32
  28. package/dist/coValueCore/verifiedState.js.map +1 -1
  29. package/dist/coValues/coList.d.ts +3 -4
  30. package/dist/coValues/coList.d.ts.map +1 -1
  31. package/dist/coValues/coList.js +4 -4
  32. package/dist/coValues/coList.js.map +1 -1
  33. package/dist/coValues/coMap.d.ts +3 -4
  34. package/dist/coValues/coMap.d.ts.map +1 -1
  35. package/dist/coValues/coMap.js +5 -4
  36. package/dist/coValues/coMap.js.map +1 -1
  37. package/dist/coValues/coStream.d.ts +3 -3
  38. package/dist/coValues/coStream.d.ts.map +1 -1
  39. package/dist/coValues/coStream.js +3 -4
  40. package/dist/coValues/coStream.js.map +1 -1
  41. package/dist/coValues/group.d.ts +3 -3
  42. package/dist/coValues/group.d.ts.map +1 -1
  43. package/dist/coValues/group.js +74 -52
  44. package/dist/coValues/group.js.map +1 -1
  45. package/dist/exports.d.ts +2 -2
  46. package/dist/exports.d.ts.map +1 -1
  47. package/dist/exports.js +2 -2
  48. package/dist/exports.js.map +1 -1
  49. package/dist/localNode.d.ts.map +1 -1
  50. package/dist/localNode.js +7 -5
  51. package/dist/localNode.js.map +1 -1
  52. package/dist/permissions.d.ts +5 -1
  53. package/dist/permissions.d.ts.map +1 -1
  54. package/dist/permissions.js +173 -109
  55. package/dist/permissions.js.map +1 -1
  56. package/dist/sync.d.ts.map +1 -1
  57. package/dist/sync.js +33 -44
  58. package/dist/sync.js.map +1 -1
  59. package/dist/tests/PeerKnownState.test.d.ts +2 -0
  60. package/dist/tests/PeerKnownState.test.d.ts.map +1 -0
  61. package/dist/tests/PeerKnownState.test.js +342 -0
  62. package/dist/tests/PeerKnownState.test.js.map +1 -0
  63. package/dist/tests/PeerState.test.js +17 -16
  64. package/dist/tests/PeerState.test.js.map +1 -1
  65. package/dist/tests/StorageApiAsync.test.js +12 -12
  66. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  67. package/dist/tests/StorageApiSync.test.js +11 -11
  68. package/dist/tests/StorageApiSync.test.js.map +1 -1
  69. package/dist/tests/SyncStateManager.test.js +16 -21
  70. package/dist/tests/SyncStateManager.test.js.map +1 -1
  71. package/dist/tests/coValueCore.dependencies.test.js +59 -0
  72. package/dist/tests/coValueCore.dependencies.test.js.map +1 -1
  73. package/dist/tests/coValueCore.test.js +41 -21
  74. package/dist/tests/coValueCore.test.js.map +1 -1
  75. package/dist/tests/group.addMember.test.js +266 -219
  76. package/dist/tests/group.addMember.test.js.map +1 -1
  77. package/dist/tests/group.inheritance.test.js +12 -0
  78. package/dist/tests/group.inheritance.test.js.map +1 -1
  79. package/dist/tests/group.invite.test.js +77 -0
  80. package/dist/tests/group.invite.test.js.map +1 -1
  81. package/dist/tests/group.removeMember.test.js +65 -8
  82. package/dist/tests/group.removeMember.test.js.map +1 -1
  83. package/dist/tests/group.roleOf.test.js +14 -4
  84. package/dist/tests/group.roleOf.test.js.map +1 -1
  85. package/dist/tests/permissions.test.js +51 -202
  86. package/dist/tests/permissions.test.js.map +1 -1
  87. package/dist/tests/sync.content.test.js +2 -2
  88. package/dist/tests/sync.content.test.js.map +1 -1
  89. package/dist/tests/sync.invite.test.js +6 -6
  90. package/dist/tests/sync.load.test.js +22 -22
  91. package/dist/tests/sync.mesh.test.js +9 -9
  92. package/dist/tests/sync.storage.test.js +13 -7
  93. package/dist/tests/sync.storage.test.js.map +1 -1
  94. package/dist/tests/sync.storageAsync.test.js +3 -3
  95. package/dist/tests/sync.test.js +13 -33
  96. package/dist/tests/sync.test.js.map +1 -1
  97. package/dist/tests/sync.upload.test.js +2 -2
  98. package/package.json +3 -3
  99. package/src/PeerState.ts +86 -34
  100. package/src/SyncStateManager.ts +25 -60
  101. package/src/coValueContentMessage.ts +3 -1
  102. package/src/coValueCore/PeerKnownState.ts +74 -0
  103. package/src/coValueCore/coValueCore.ts +180 -49
  104. package/src/coValueCore/decryptTransactionChangesAndMeta.ts +0 -6
  105. package/src/coValueCore/verifiedState.ts +2 -37
  106. package/src/coValues/coList.ts +7 -7
  107. package/src/coValues/coMap.ts +9 -7
  108. package/src/coValues/coStream.ts +6 -5
  109. package/src/coValues/group.ts +99 -60
  110. package/src/exports.ts +2 -1
  111. package/src/localNode.ts +7 -5
  112. package/src/permissions.ts +204 -123
  113. package/src/sync.ts +37 -53
  114. package/src/tests/PeerKnownState.test.ts +426 -0
  115. package/src/tests/PeerState.test.ts +24 -24
  116. package/src/tests/StorageApiAsync.test.ts +12 -12
  117. package/src/tests/StorageApiSync.test.ts +11 -11
  118. package/src/tests/SyncStateManager.test.ts +23 -53
  119. package/src/tests/coValueCore.dependencies.test.ts +87 -0
  120. package/src/tests/coValueCore.test.ts +64 -22
  121. package/src/tests/group.addMember.test.ts +384 -345
  122. package/src/tests/group.inheritance.test.ts +33 -0
  123. package/src/tests/group.invite.test.ts +117 -0
  124. package/src/tests/group.removeMember.test.ts +96 -10
  125. package/src/tests/group.roleOf.test.ts +16 -4
  126. package/src/tests/permissions.test.ts +56 -295
  127. package/src/tests/sync.content.test.ts +2 -2
  128. package/src/tests/sync.invite.test.ts +6 -6
  129. package/src/tests/sync.load.test.ts +22 -22
  130. package/src/tests/sync.mesh.test.ts +9 -9
  131. package/src/tests/sync.storage.test.ts +13 -8
  132. package/src/tests/sync.storageAsync.test.ts +3 -3
  133. package/src/tests/sync.test.ts +21 -50
  134. package/src/tests/sync.upload.test.ts +2 -2
  135. package/dist/PeerKnownStates.d.ts +0 -19
  136. package/dist/PeerKnownStates.d.ts.map +0 -1
  137. package/dist/PeerKnownStates.js +0 -64
  138. package/dist/PeerKnownStates.js.map +0 -1
  139. package/dist/tests/PeerKnownStates.test.d.ts +0 -2
  140. package/dist/tests/PeerKnownStates.test.d.ts.map +0 -1
  141. package/dist/tests/PeerKnownStates.test.js +0 -77
  142. package/dist/tests/PeerKnownStates.test.js.map +0 -1
  143. package/src/PeerKnownStates.ts +0 -93
  144. package/src/tests/PeerKnownStates.test.ts +0 -99
package/src/sync.ts CHANGED
@@ -229,7 +229,7 @@ export class SyncManager {
229
229
  }
230
230
 
231
231
  const newContentPieces = coValue.verified.newContentSince(
232
- peer.optimisticKnownStates.get(id),
232
+ peer.getOptimisticKnownState(id),
233
233
  );
234
234
 
235
235
  if (newContentPieces) {
@@ -306,8 +306,8 @@ export class SyncManager {
306
306
  }
307
307
 
308
308
  // Fill the missing known states with empty known states
309
- if (!peer.optimisticKnownStates.has(coValue.id)) {
310
- peer.setOptimisticKnownState(coValue.id, "empty");
309
+ if (!peer.getKnownState(coValue.id)) {
310
+ peer.setKnownState(coValue.id, "empty");
311
311
  }
312
312
  }
313
313
 
@@ -342,20 +342,18 @@ export class SyncManager {
342
342
  addPeer(peer: Peer, skipReconciliation: boolean = false) {
343
343
  const prevPeer = this.peers[peer.id];
344
344
 
345
- if (prevPeer && !prevPeer.closed) {
346
- prevPeer.gracefulShutdown();
347
- }
345
+ const peerState = prevPeer
346
+ ? prevPeer.newPeerStateFrom(peer)
347
+ : new PeerState(peer, undefined);
348
348
 
349
- const peerState = new PeerState(peer, prevPeer?.knownStates);
350
349
  this.peers[peer.id] = peerState;
351
350
 
352
351
  this.peersCounter.add(1, { role: peer.role });
353
352
 
354
- const unsubscribeFromKnownStatesUpdates = peerState.knownStates.subscribe(
355
- (id) => {
356
- this.syncState.triggerUpdate(peer.id, id);
357
- },
358
- );
353
+ const unsubscribeFromKnownStatesUpdates =
354
+ peerState.subscribeToKnownStatesUpdates((id, knownState) => {
355
+ this.syncState.triggerUpdate(peer.id, id, knownState.value());
356
+ });
359
357
 
360
358
  if (!skipReconciliation && peerState.role === "server") {
361
359
  void this.startPeerReconciliation(peerState);
@@ -450,7 +448,7 @@ export class SyncManager {
450
448
 
451
449
  // The header is a boolean value that tells us if the other peer do have information about the header.
452
450
  // If it's false in this point it means that the coValue is unavailable on the other peer.
453
- const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
451
+ const availableOnPeer = peer.getOptimisticKnownState(msg.id)?.header;
454
452
 
455
453
  if (!availableOnPeer) {
456
454
  coValue.markNotFoundInPeer(peer.id);
@@ -598,7 +596,7 @@ export class SyncManager {
598
596
 
599
597
  let invalidStateAssumed = false;
600
598
 
601
- const contentToStore: NewContentMessage = {
599
+ const validNewContent: NewContentMessage = {
602
600
  action: "content",
603
601
  id: msg.id,
604
602
  priority: msg.priority,
@@ -667,13 +665,12 @@ export class SyncManager {
667
665
 
668
666
  // The new content for this session has been verified, so we can store it
669
667
  if (result.value) {
670
- contentToStore.new[sessionID] = newContentForSession;
668
+ validNewContent.new[sessionID] = newContentForSession;
671
669
  }
670
+ }
672
671
 
673
- const transactionsCount =
674
- newContentForSession.after +
675
- newContentForSession.newTransactions.length;
676
- peer?.updateSessionCounter(msg.id, sessionID, transactionsCount);
672
+ if (peer) {
673
+ peer.combineWith(msg.id, knownStateFromContent(validNewContent));
677
674
  }
678
675
 
679
676
  /**
@@ -718,10 +715,10 @@ export class SyncManager {
718
715
  * Store the content and propagate it to the server peers and the subscribed client peers
719
716
  */
720
717
  const hasNewContent =
721
- contentToStore.header || Object.keys(contentToStore.new).length > 0;
718
+ validNewContent.header || Object.keys(validNewContent.new).length > 0;
722
719
 
723
720
  if (from !== "storage" && hasNewContent) {
724
- this.storeContent(contentToStore);
721
+ this.storeContent(validNewContent);
725
722
  }
726
723
 
727
724
  for (const peer of this.getPeers(coValue.id)) {
@@ -729,11 +726,13 @@ export class SyncManager {
729
726
  * We sync the content against the source peer if it is a client or server peers
730
727
  * to upload any content that is available on the current node and not on the source peer.
731
728
  */
732
- if (peer.closed) continue;
733
- if (coValue.isErroredInPeer(peer.id)) continue;
729
+ if (peer.closed || coValue.isErroredInPeer(peer.id)) {
730
+ peer.emitCoValueChange(coValue.id);
731
+ continue;
732
+ }
734
733
 
735
734
  // We directly forward the new content to peers that have an active subscription
736
- if (peer.optimisticKnownStates.has(coValue.id)) {
735
+ if (peer.isCoValueSubscribedToPeer(coValue.id)) {
737
736
  this.sendNewContent(coValue.id, peer);
738
737
  syncedPeers.push(peer);
739
738
  } else if (
@@ -756,13 +755,6 @@ export class SyncManager {
756
755
  }
757
756
  }
758
757
  }
759
-
760
- /**
761
- * Send an update to all the sync state listeners
762
- */
763
- for (const peer of syncedPeers) {
764
- this.syncState.triggerUpdate(peer.id, coValue.id);
765
- }
766
758
  }
767
759
 
768
760
  handleCorrection(msg: KnownStateMessage, peer: PeerState) {
@@ -786,27 +778,25 @@ export class SyncManager {
786
778
  const contentKnownState = knownStateFromContent(content);
787
779
 
788
780
  for (const peer of this.getPeers(coValue.id)) {
789
- if (peer.closed) continue;
790
- if (coValue.isErroredInPeer(peer.id)) continue;
791
-
792
781
  // Only subscribed CoValues are synced to clients
793
782
  if (
794
783
  peer.role === "client" &&
795
- !peer.optimisticKnownStates.has(coValue.id)
784
+ !peer.isCoValueSubscribedToPeer(coValue.id)
796
785
  ) {
797
786
  continue;
798
787
  }
799
788
 
789
+ if (peer.closed || coValue.isErroredInPeer(peer.id)) {
790
+ peer.emitCoValueChange(content.id);
791
+ continue;
792
+ }
793
+
800
794
  // We assume that the peer already knows anything before this content
801
795
  // Any eventual reconciliation will be handled through the known state messages exchange
802
796
  this.trySendToPeer(peer, content);
803
797
  peer.combineOptimisticWith(coValue.id, contentKnownState);
804
798
  peer.trackToldKnownState(coValue.id);
805
799
  }
806
-
807
- for (const peer of this.getPeers(coValue.id)) {
808
- this.syncState.triggerUpdate(peer.id, coValue.id);
809
- }
810
800
  }
811
801
 
812
802
  private storeContent(content: NewContentMessage) {
@@ -837,15 +827,6 @@ export class SyncManager {
837
827
  }
838
828
 
839
829
  waitForSyncWithPeer(peerId: PeerID, id: RawCoID, timeout: number) {
840
- const { syncState } = this;
841
- const currentSyncState = syncState.getCurrentSyncState(peerId, id);
842
-
843
- const isTheConditionAlreadyMet = currentSyncState.uploaded;
844
-
845
- if (isTheConditionAlreadyMet) {
846
- return;
847
- }
848
-
849
830
  const peerState = this.peers[peerId];
850
831
 
851
832
  // The peer has been closed and is not persistent, so it isn't possible to sync
@@ -853,11 +834,14 @@ export class SyncManager {
853
834
  return;
854
835
  }
855
836
 
856
- // The client isn't subscribed to the coValue, so we won't sync it
857
- if (
858
- peerState.role === "client" &&
859
- !peerState.optimisticKnownStates.has(id)
860
- ) {
837
+ if (peerState.isCoValueSubscribedToPeer(id)) {
838
+ const isAlreadySynced = this.syncState.isSynced(peerState, id);
839
+
840
+ if (isAlreadySynced) {
841
+ return;
842
+ }
843
+ } else if (peerState.role === "client") {
844
+ // The client isn't subscribed to the coValue, so we won't sync it
861
845
  return;
862
846
  }
863
847
 
@@ -0,0 +1,426 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { PeerKnownState } from "../coValueCore/PeerKnownState.js";
3
+ import type { CoValueKnownState } from "../knownState.js";
4
+ import { cloneKnownState } from "../knownState.js";
5
+ import { RawCoID, SessionID } from "../ids.js";
6
+ import type { PeerID } from "../sync.js";
7
+
8
+ describe("PeerKnownState", () => {
9
+ const testId = "co_ztest123" as RawCoID;
10
+ const testPeerId = "peer123" as PeerID;
11
+ const session1 = "session1_session_z123" as SessionID;
12
+ const session2 = "session2_session_z456" as SessionID;
13
+
14
+ describe("constructor", () => {
15
+ test("should initialize with correct peerId and empty knownState", () => {
16
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
17
+
18
+ expect(peerKnownState.peerId).toBe(testPeerId);
19
+ expect(peerKnownState.value()).toEqual({
20
+ id: testId,
21
+ header: false,
22
+ sessions: {},
23
+ });
24
+ expect(peerKnownState.optimisticValue()).toEqual({
25
+ id: testId,
26
+ header: false,
27
+ sessions: {},
28
+ });
29
+ });
30
+
31
+ test("should maintain knownState reference", () => {
32
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
33
+ const initialKnownState = peerKnownState.value();
34
+
35
+ // Verify that the reference is preserved by checking that modifications
36
+ // to the returned object affect subsequent calls to value()
37
+ const state1 = peerKnownState.value();
38
+ const state2 = peerKnownState.value();
39
+ expect(state1).toBe(state2); // Same reference
40
+ expect(state1).toBe(initialKnownState); // Same as initial reference
41
+ });
42
+ });
43
+
44
+ describe("updateHeader", () => {
45
+ test("should update header in knownState when no optimistic state exists", () => {
46
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
47
+ const initialKnownState = peerKnownState.value();
48
+
49
+ peerKnownState.updateHeader(true);
50
+
51
+ expect(peerKnownState.value().header).toBe(true);
52
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
53
+ });
54
+
55
+ test("should update header in both knownState and optimisticKnownState", () => {
56
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
57
+
58
+ // Create optimistic state first
59
+ peerKnownState.combineOptimisticWith({
60
+ id: testId,
61
+ header: false,
62
+ sessions: { [session1]: 5 },
63
+ });
64
+
65
+ const knownStateRef = peerKnownState.value();
66
+ const optimisticStateRef = peerKnownState.optimisticValue();
67
+
68
+ peerKnownState.updateHeader(true);
69
+
70
+ expect(peerKnownState.value().header).toBe(true);
71
+ expect(peerKnownState.optimisticValue().header).toBe(true);
72
+ expect(peerKnownState.value()).toBe(knownStateRef); // Reference preserved
73
+ expect(peerKnownState.optimisticValue()).toBe(optimisticStateRef); // Reference preserved
74
+ });
75
+
76
+ test("should update header to false", () => {
77
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
78
+ peerKnownState.updateHeader(true);
79
+
80
+ peerKnownState.updateHeader(false);
81
+
82
+ expect(peerKnownState.value().header).toBe(false);
83
+ expect(peerKnownState.optimisticValue().header).toBe(false);
84
+ });
85
+ });
86
+
87
+ describe("combineWith", () => {
88
+ test("should combine sessions and header correctly", () => {
89
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
90
+ const initialKnownState = peerKnownState.value();
91
+
92
+ const toCombine: CoValueKnownState = {
93
+ id: testId,
94
+ header: true,
95
+ sessions: { [session1]: 5, [session2]: 10 },
96
+ };
97
+
98
+ peerKnownState.combineWith(toCombine);
99
+
100
+ expect(peerKnownState.value()).toEqual({
101
+ id: testId,
102
+ header: true,
103
+ sessions: { [session1]: 5, [session2]: 10 },
104
+ });
105
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
106
+ });
107
+
108
+ test("should update existing sessions with higher values", () => {
109
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
110
+ const initialKnownState = peerKnownState.value();
111
+
112
+ // Set initial state
113
+ peerKnownState.combineWith({
114
+ id: testId,
115
+ header: false,
116
+ sessions: { [session1]: 3, [session2]: 5 },
117
+ });
118
+
119
+ // Combine with higher values for session1, lower for session2
120
+ peerKnownState.combineWith({
121
+ id: testId,
122
+ header: true,
123
+ sessions: { [session1]: 7, [session2]: 2 },
124
+ });
125
+
126
+ expect(peerKnownState.value().sessions[session1]).toBe(7); // Updated to higher value
127
+ expect(peerKnownState.value().sessions[session2]).toBe(5); // Kept higher existing value
128
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
129
+ });
130
+
131
+ test("should update optimisticKnownState when it exists", () => {
132
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
133
+
134
+ // Create optimistic state
135
+ peerKnownState.combineOptimisticWith({
136
+ id: testId,
137
+ header: false,
138
+ sessions: { [session1]: 3 },
139
+ });
140
+
141
+ const optimisticStateRef = peerKnownState.optimisticValue();
142
+
143
+ peerKnownState.combineWith({
144
+ id: testId,
145
+ header: true,
146
+ sessions: { [session2]: 10 },
147
+ });
148
+
149
+ expect(peerKnownState.optimisticValue().sessions[session1]).toBe(3); // From optimistic state
150
+ expect(peerKnownState.optimisticValue().sessions[session2]).toBe(10); // Combined
151
+ expect(peerKnownState.optimisticValue().header).toBe(true); // Combined header
152
+ expect(peerKnownState.optimisticValue()).toBe(optimisticStateRef); // Reference preserved
153
+ });
154
+ });
155
+
156
+ describe("combineOptimisticWith", () => {
157
+ test("should create optimisticKnownState when none exists", () => {
158
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
159
+ const initialKnownState = peerKnownState.value();
160
+
161
+ const toCombine: CoValueKnownState = {
162
+ id: testId,
163
+ header: true,
164
+ sessions: { [session1]: 5 },
165
+ };
166
+
167
+ peerKnownState.combineOptimisticWith(toCombine);
168
+
169
+ expect(peerKnownState.optimisticValue()).toEqual({
170
+ id: testId,
171
+ header: true,
172
+ sessions: { [session1]: 5 },
173
+ });
174
+ // Should be a different object from knownState
175
+ expect(peerKnownState.optimisticValue()).not.toBe(peerKnownState.value());
176
+ // Should not affect the known state
177
+ expect(peerKnownState.value()).toEqual(initialKnownState);
178
+ });
179
+
180
+ test("should combine with existing optimisticKnownState", () => {
181
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
182
+
183
+ // Create initial optimistic state
184
+ peerKnownState.combineOptimisticWith({
185
+ id: testId,
186
+ header: false,
187
+ sessions: { [session1]: 3 },
188
+ });
189
+
190
+ const optimisticStateRef = peerKnownState.optimisticValue();
191
+
192
+ // Combine with additional state
193
+ peerKnownState.combineOptimisticWith({
194
+ id: testId,
195
+ header: true,
196
+ sessions: { [session2]: 7 },
197
+ });
198
+
199
+ expect(peerKnownState.optimisticValue()).toEqual({
200
+ id: testId,
201
+ header: true,
202
+ sessions: { [session1]: 3, [session2]: 7 },
203
+ });
204
+ expect(peerKnownState.optimisticValue()).toBe(optimisticStateRef); // Reference preserved
205
+ });
206
+
207
+ test("should preserve knownState when combining optimistic", () => {
208
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
209
+ const initialKnownState = peerKnownState.value();
210
+
211
+ peerKnownState.combineOptimisticWith({
212
+ id: testId,
213
+ header: true,
214
+ sessions: { [session1]: 10 },
215
+ });
216
+
217
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
218
+ expect(peerKnownState.value().sessions).toEqual({}); // Known state unchanged
219
+ });
220
+ });
221
+
222
+ describe("set", () => {
223
+ test("should set knownState with provided CoValueKnownState", () => {
224
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
225
+ const initialKnownState = peerKnownState.value();
226
+
227
+ // Create optimistic state first
228
+ peerKnownState.combineOptimisticWith({
229
+ id: testId,
230
+ header: false,
231
+ sessions: { [session1]: 5 },
232
+ });
233
+
234
+ const newState: CoValueKnownState = {
235
+ id: testId,
236
+ header: true,
237
+ sessions: { [session1]: 10, [session2]: 20 },
238
+ };
239
+
240
+ peerKnownState.set(newState);
241
+
242
+ expect(peerKnownState.value()).toEqual(newState);
243
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
244
+ expect(peerKnownState.optimisticValue()).toBe(peerKnownState.value()); // Should return knownState when optimistic is cleared
245
+ });
246
+
247
+ test("should handle shallow copy of sessions in set", () => {
248
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
249
+ const initialKnownState = peerKnownState.value();
250
+
251
+ const sessions = { [session1]: 5, [session2]: 10 };
252
+ const newState: CoValueKnownState = {
253
+ id: testId,
254
+ header: true,
255
+ sessions,
256
+ };
257
+
258
+ peerKnownState.set(newState);
259
+
260
+ expect(peerKnownState.value().sessions).toEqual(sessions);
261
+ expect(peerKnownState.value().sessions).not.toBe(sessions); // Should be a copy
262
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
263
+ });
264
+
265
+ test("should set empty state when payload is 'empty'", () => {
266
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
267
+
268
+ // Set some initial state
269
+ peerKnownState.combineWith({
270
+ id: testId,
271
+ header: true,
272
+ sessions: { [session1]: 5 },
273
+ });
274
+
275
+ // Create optimistic state
276
+ peerKnownState.combineOptimisticWith({
277
+ id: testId,
278
+ header: false,
279
+ sessions: { [session2]: 10 },
280
+ });
281
+
282
+ const initialKnownState = peerKnownState.value();
283
+ peerKnownState.set("empty");
284
+
285
+ expect(peerKnownState.value()).toEqual({
286
+ id: testId,
287
+ header: false,
288
+ sessions: {},
289
+ });
290
+ expect(peerKnownState.value()).toBe(initialKnownState); // Reference preserved
291
+ expect(peerKnownState.optimisticValue()).toBe(peerKnownState.value()); // Should return knownState when optimistic is cleared
292
+ });
293
+
294
+ test("should clear optimisticKnownState on set", () => {
295
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
296
+
297
+ // Create optimistic state
298
+ peerKnownState.combineOptimisticWith({
299
+ id: testId,
300
+ header: true,
301
+ sessions: { [session1]: 5 },
302
+ });
303
+
304
+ expect(peerKnownState.optimisticValue()).toBeDefined();
305
+ expect(peerKnownState.optimisticValue()).not.toBe(peerKnownState.value());
306
+
307
+ peerKnownState.set("empty");
308
+
309
+ expect(peerKnownState.optimisticValue()).toBe(peerKnownState.value()); // Should return knownState when optimistic is cleared
310
+ });
311
+ });
312
+
313
+ describe("value", () => {
314
+ test("should return the current knownState", () => {
315
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
316
+ const state1 = peerKnownState.value();
317
+ const state2 = peerKnownState.value();
318
+
319
+ expect(state1).toBe(state2); // Same reference
320
+ expect(state1).toEqual({
321
+ id: testId,
322
+ header: false,
323
+ sessions: {},
324
+ });
325
+ });
326
+
327
+ test("should return updated knownState after modifications", () => {
328
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
329
+
330
+ peerKnownState.combineWith({
331
+ id: testId,
332
+ header: true,
333
+ sessions: { [session1]: 5 },
334
+ });
335
+
336
+ expect(peerKnownState.value()).toEqual({
337
+ id: testId,
338
+ header: true,
339
+ sessions: { [session1]: 5 },
340
+ });
341
+ });
342
+ });
343
+
344
+ describe("optimisticValue", () => {
345
+ test("should return knownState when no optimistic state exists", () => {
346
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
347
+
348
+ expect(peerKnownState.optimisticValue()).toBe(peerKnownState.value());
349
+ });
350
+
351
+ test("should return optimisticKnownState when it exists", () => {
352
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
353
+
354
+ peerKnownState.combineOptimisticWith({
355
+ id: testId,
356
+ header: true,
357
+ sessions: { [session1]: 5 },
358
+ });
359
+
360
+ const optimisticState = peerKnownState.optimisticValue();
361
+ expect(optimisticState).not.toBe(peerKnownState.value());
362
+ expect(optimisticState).toEqual({
363
+ id: testId,
364
+ header: true,
365
+ sessions: { [session1]: 5 },
366
+ });
367
+ });
368
+
369
+ test("should maintain reference consistency", () => {
370
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
371
+
372
+ peerKnownState.combineOptimisticWith({
373
+ id: testId,
374
+ header: true,
375
+ sessions: { [session1]: 5 },
376
+ });
377
+
378
+ const optimisticState1 = peerKnownState.optimisticValue();
379
+ const optimisticState2 = peerKnownState.optimisticValue();
380
+
381
+ expect(optimisticState1).toBe(optimisticState2); // Same reference
382
+ });
383
+ });
384
+
385
+ describe("integration scenarios", () => {
386
+ test("should handle complex workflow maintaining references", () => {
387
+ const peerKnownState = new PeerKnownState(testId, testPeerId);
388
+ const originalKnownState = peerKnownState.value();
389
+
390
+ // Initial state setup
391
+ peerKnownState.updateHeader(true);
392
+ expect(peerKnownState.value()).toBe(originalKnownState);
393
+
394
+ // Combine with some sessions
395
+ peerKnownState.combineWith({
396
+ id: testId,
397
+ header: false,
398
+ sessions: { [session1]: 3, [session2]: 7 },
399
+ });
400
+ expect(peerKnownState.value()).toBe(originalKnownState);
401
+
402
+ // Create optimistic state
403
+ peerKnownState.combineOptimisticWith({
404
+ id: testId,
405
+ header: true,
406
+ sessions: { [session2]: 5 },
407
+ });
408
+
409
+ const originalOptimisticState = peerKnownState.optimisticValue();
410
+
411
+ // Update header should affect both states
412
+ peerKnownState.updateHeader(false);
413
+ expect(peerKnownState.value()).toBe(originalKnownState);
414
+ expect(peerKnownState.optimisticValue()).toBe(originalOptimisticState);
415
+
416
+ // Set should clear optimistic and update known
417
+ peerKnownState.set({
418
+ id: testId,
419
+ header: true,
420
+ sessions: { [session1]: 20 },
421
+ });
422
+ expect(peerKnownState.value()).toBe(originalKnownState);
423
+ expect(peerKnownState.optimisticValue()).toBe(peerKnownState.value()); // Should return knownState when optimistic is cleared
424
+ });
425
+ });
426
+ });
@@ -44,7 +44,7 @@ describe("PeerState", () => {
44
44
  expect(peerState.closed).toBe(true);
45
45
  });
46
46
 
47
- test("should clone the knownStates into optimisticKnownStates and knownStates when passed as argument", () => {
47
+ test("should clone the knownStates and reset the optimistic known state when using newPeerStateFrom", () => {
48
48
  const { peerState, mockPeer } = setup();
49
49
  peerState.setKnownState("co_z1", {
50
50
  id: "co_z1",
@@ -52,31 +52,40 @@ describe("PeerState", () => {
52
52
  sessions: {},
53
53
  });
54
54
 
55
- const newPeerState = new PeerState(mockPeer, peerState.knownStates);
55
+ peerState.combineOptimisticWith("co_z1", {
56
+ id: "co_z1",
57
+ header: true,
58
+ sessions: {
59
+ "session-1": 1,
60
+ } as KnownStateSessions,
61
+ });
62
+
63
+ const newPeerState = peerState.newPeerStateFrom(mockPeer);
56
64
 
57
- expect(newPeerState.knownStates).toEqual(peerState.knownStates);
58
- expect(newPeerState.optimisticKnownStates).toEqual(peerState.knownStates);
65
+ expect(newPeerState.getKnownState("co_z1")).toEqual(
66
+ peerState.getKnownState("co_z1"),
67
+ );
68
+ expect(newPeerState.getKnownState("co_z1")).not.toBe(
69
+ peerState.getKnownState("co_z1"),
70
+ );
71
+ expect(newPeerState.getOptimisticKnownState("co_z1")).toBe(
72
+ newPeerState.getKnownState("co_z1"),
73
+ );
59
74
  });
60
75
 
61
76
  test("should dispatch to both states", () => {
62
77
  const { peerState } = setup();
63
- const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
64
-
65
- const optimisticKnownStatesSpy = vi.spyOn(
66
- peerState._optimisticKnownStates,
67
- "set",
68
- );
69
78
 
70
79
  const state: CoValueKnownState = {
71
80
  id: "co_z1",
72
- header: false,
81
+ header: true,
73
82
  sessions: {},
74
83
  };
75
84
 
76
85
  peerState.setKnownState("co_z1", state);
77
86
 
78
- expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
79
- expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
87
+ expect(peerState.getKnownState("co_z1")).toEqual(state);
88
+ expect(peerState.getOptimisticKnownState("co_z1")).toEqual(state);
80
89
  });
81
90
 
82
91
  test("dispatching an optimistic update should not affect the known states", () => {
@@ -100,16 +109,7 @@ describe("PeerState", () => {
100
109
 
101
110
  peerState.combineOptimisticWith("co_z1", optimisticState);
102
111
 
103
- expect(peerState.knownStates.get("co_z1")).not.toEqual(optimisticState);
104
- expect(peerState.optimisticKnownStates.get("co_z1")).toEqual(
105
- optimisticState,
106
- );
107
- });
108
-
109
- test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
110
- const { peerState } = setup(); // Uses a regular peer
111
-
112
- // Verify they are different references
113
- expect(peerState.knownStates).not.toBe(peerState.optimisticKnownStates);
112
+ expect(peerState.getKnownState("co_z1")).not.toEqual(optimisticState);
113
+ expect(peerState.getOptimisticKnownState("co_z1")).toEqual(optimisticState);
114
114
  });
115
115
  });