cojson 0.8.12 → 0.8.17

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 (164) hide show
  1. package/CHANGELOG.md +95 -83
  2. package/dist/native/PeerKnownStates.js +6 -1
  3. package/dist/native/PeerKnownStates.js.map +1 -1
  4. package/dist/native/PeerState.js +4 -3
  5. package/dist/native/PeerState.js.map +1 -1
  6. package/dist/native/PriorityBasedMessageQueue.js +1 -10
  7. package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
  8. package/dist/native/SyncStateSubscriptionManager.js +70 -0
  9. package/dist/native/SyncStateSubscriptionManager.js.map +1 -0
  10. package/dist/native/base64url.js.map +1 -1
  11. package/dist/native/base64url.test.js +1 -1
  12. package/dist/native/base64url.test.js.map +1 -1
  13. package/dist/native/coValue.js.map +1 -1
  14. package/dist/native/coValueCore.js +141 -149
  15. package/dist/native/coValueCore.js.map +1 -1
  16. package/dist/native/coValueState.js.map +1 -1
  17. package/dist/native/coValues/account.js +6 -6
  18. package/dist/native/coValues/account.js.map +1 -1
  19. package/dist/native/coValues/coList.js +2 -3
  20. package/dist/native/coValues/coList.js.map +1 -1
  21. package/dist/native/coValues/coMap.js +1 -1
  22. package/dist/native/coValues/coMap.js.map +1 -1
  23. package/dist/native/coValues/coStream.js +3 -5
  24. package/dist/native/coValues/coStream.js.map +1 -1
  25. package/dist/native/coValues/group.js +11 -11
  26. package/dist/native/coValues/group.js.map +1 -1
  27. package/dist/native/coreToCoValue.js +2 -2
  28. package/dist/native/coreToCoValue.js.map +1 -1
  29. package/dist/native/crypto/PureJSCrypto.js +4 -4
  30. package/dist/native/crypto/PureJSCrypto.js.map +1 -1
  31. package/dist/native/crypto/crypto.js.map +1 -1
  32. package/dist/native/exports.js +12 -12
  33. package/dist/native/exports.js.map +1 -1
  34. package/dist/native/ids.js.map +1 -1
  35. package/dist/native/jsonStringify.js.map +1 -1
  36. package/dist/native/localNode.js +5 -7
  37. package/dist/native/localNode.js.map +1 -1
  38. package/dist/native/permissions.js +4 -7
  39. package/dist/native/permissions.js.map +1 -1
  40. package/dist/native/priority.js.map +1 -1
  41. package/dist/native/storage/FileSystem.js.map +1 -1
  42. package/dist/native/storage/chunksAndKnownStates.js +2 -4
  43. package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
  44. package/dist/native/storage/index.js +6 -15
  45. package/dist/native/storage/index.js.map +1 -1
  46. package/dist/native/streamUtils.js.map +1 -1
  47. package/dist/native/sync.js +57 -7
  48. package/dist/native/sync.js.map +1 -1
  49. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  50. package/dist/native/typeUtils/expectGroup.js.map +1 -1
  51. package/dist/native/typeUtils/isAccountID.js.map +1 -1
  52. package/dist/native/typeUtils/isCoValue.js +1 -1
  53. package/dist/native/typeUtils/isCoValue.js.map +1 -1
  54. package/dist/web/PeerKnownStates.js +6 -1
  55. package/dist/web/PeerKnownStates.js.map +1 -1
  56. package/dist/web/PeerState.js +4 -3
  57. package/dist/web/PeerState.js.map +1 -1
  58. package/dist/web/PriorityBasedMessageQueue.js +1 -10
  59. package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
  60. package/dist/web/SyncStateSubscriptionManager.js +70 -0
  61. package/dist/web/SyncStateSubscriptionManager.js.map +1 -0
  62. package/dist/web/base64url.js.map +1 -1
  63. package/dist/web/base64url.test.js +1 -1
  64. package/dist/web/base64url.test.js.map +1 -1
  65. package/dist/web/coValue.js.map +1 -1
  66. package/dist/web/coValueCore.js +141 -149
  67. package/dist/web/coValueCore.js.map +1 -1
  68. package/dist/web/coValueState.js.map +1 -1
  69. package/dist/web/coValues/account.js +6 -6
  70. package/dist/web/coValues/account.js.map +1 -1
  71. package/dist/web/coValues/coList.js +2 -3
  72. package/dist/web/coValues/coList.js.map +1 -1
  73. package/dist/web/coValues/coMap.js +1 -1
  74. package/dist/web/coValues/coMap.js.map +1 -1
  75. package/dist/web/coValues/coStream.js +3 -5
  76. package/dist/web/coValues/coStream.js.map +1 -1
  77. package/dist/web/coValues/group.js +11 -11
  78. package/dist/web/coValues/group.js.map +1 -1
  79. package/dist/web/coreToCoValue.js +2 -2
  80. package/dist/web/coreToCoValue.js.map +1 -1
  81. package/dist/web/crypto/PureJSCrypto.js +4 -4
  82. package/dist/web/crypto/PureJSCrypto.js.map +1 -1
  83. package/dist/web/crypto/WasmCrypto.js +5 -5
  84. package/dist/web/crypto/WasmCrypto.js.map +1 -1
  85. package/dist/web/crypto/crypto.js.map +1 -1
  86. package/dist/web/exports.js +12 -12
  87. package/dist/web/exports.js.map +1 -1
  88. package/dist/web/ids.js.map +1 -1
  89. package/dist/web/jsonStringify.js.map +1 -1
  90. package/dist/web/localNode.js +5 -7
  91. package/dist/web/localNode.js.map +1 -1
  92. package/dist/web/permissions.js +4 -7
  93. package/dist/web/permissions.js.map +1 -1
  94. package/dist/web/priority.js.map +1 -1
  95. package/dist/web/storage/FileSystem.js.map +1 -1
  96. package/dist/web/storage/chunksAndKnownStates.js +2 -4
  97. package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
  98. package/dist/web/storage/index.js +6 -15
  99. package/dist/web/storage/index.js.map +1 -1
  100. package/dist/web/streamUtils.js.map +1 -1
  101. package/dist/web/sync.js +57 -7
  102. package/dist/web/sync.js.map +1 -1
  103. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  104. package/dist/web/typeUtils/expectGroup.js.map +1 -1
  105. package/dist/web/typeUtils/isAccountID.js.map +1 -1
  106. package/dist/web/typeUtils/isCoValue.js +1 -1
  107. package/dist/web/typeUtils/isCoValue.js.map +1 -1
  108. package/package.json +4 -14
  109. package/src/PeerKnownStates.ts +98 -90
  110. package/src/PeerState.ts +92 -73
  111. package/src/PriorityBasedMessageQueue.ts +42 -49
  112. package/src/SyncStateSubscriptionManager.ts +124 -0
  113. package/src/base64url.test.ts +24 -24
  114. package/src/base64url.ts +44 -45
  115. package/src/coValue.ts +45 -45
  116. package/src/coValueCore.ts +746 -785
  117. package/src/coValueState.ts +82 -72
  118. package/src/coValues/account.ts +143 -150
  119. package/src/coValues/coList.ts +520 -522
  120. package/src/coValues/coMap.ts +283 -285
  121. package/src/coValues/coStream.ts +320 -324
  122. package/src/coValues/group.ts +306 -305
  123. package/src/coreToCoValue.ts +28 -31
  124. package/src/crypto/PureJSCrypto.ts +188 -194
  125. package/src/crypto/WasmCrypto.ts +236 -254
  126. package/src/crypto/crypto.ts +302 -309
  127. package/src/exports.ts +116 -116
  128. package/src/ids.ts +9 -9
  129. package/src/jsonStringify.ts +46 -46
  130. package/src/jsonValue.ts +24 -10
  131. package/src/localNode.ts +635 -660
  132. package/src/media.ts +3 -3
  133. package/src/permissions.ts +272 -278
  134. package/src/priority.ts +21 -19
  135. package/src/storage/FileSystem.ts +91 -99
  136. package/src/storage/chunksAndKnownStates.ts +110 -115
  137. package/src/storage/index.ts +466 -497
  138. package/src/streamUtils.ts +60 -60
  139. package/src/sync.ts +656 -608
  140. package/src/tests/PeerKnownStates.test.ts +38 -34
  141. package/src/tests/PeerState.test.ts +101 -64
  142. package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
  143. package/src/tests/SyncStateSubscriptionManager.test.ts +232 -0
  144. package/src/tests/account.test.ts +59 -59
  145. package/src/tests/coList.test.ts +65 -65
  146. package/src/tests/coMap.test.ts +137 -137
  147. package/src/tests/coStream.test.ts +254 -257
  148. package/src/tests/coValueCore.test.ts +153 -156
  149. package/src/tests/crypto.test.ts +136 -144
  150. package/src/tests/cryptoImpl.test.ts +205 -197
  151. package/src/tests/group.test.ts +24 -24
  152. package/src/tests/permissions.test.ts +1306 -1371
  153. package/src/tests/priority.test.ts +65 -82
  154. package/src/tests/sync.test.ts +1573 -1263
  155. package/src/tests/testUtils.ts +85 -53
  156. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
  157. package/src/typeUtils/expectGroup.ts +9 -9
  158. package/src/typeUtils/isAccountID.ts +1 -1
  159. package/src/typeUtils/isCoValue.ts +9 -9
  160. package/tsconfig.json +4 -6
  161. package/tsconfig.native.json +9 -11
  162. package/tsconfig.web.json +4 -10
  163. package/.eslintrc.cjs +0 -25
  164. package/.prettierrc.js +0 -9
@@ -1,108 +1,116 @@
1
1
  import { RawCoID, SessionID } from "./ids.js";
2
- import { CoValueKnownState, emptyKnownState, combinedKnownStates } from "./sync.js";
3
-
4
- type PeerKnownStateActions = {
5
- type: "SET_AS_EMPTY";
6
- id: RawCoID;
7
- } | {
8
- type: "UPDATE_HEADER";
9
- id: RawCoID;
10
- header: boolean;
11
- } |
12
- {
13
- type: "UPDATE_SESSION_COUNTER";
14
- id: RawCoID;
15
- sessionId: SessionID;
16
- value: number;
17
- } |
18
- {
19
- type: "SET";
20
- id: RawCoID;
21
- value: CoValueKnownState;
22
- } |
23
- {
24
- type: "COMBINE_WITH";
25
- id: RawCoID;
26
- value: CoValueKnownState;
27
- };
2
+ import {
3
+ CoValueKnownState,
4
+ combinedKnownStates,
5
+ emptyKnownState,
6
+ } from "./sync.js";
7
+
8
+ type PeerKnownStateActions =
9
+ | {
10
+ type: "SET_AS_EMPTY";
11
+ id: RawCoID;
12
+ }
13
+ | {
14
+ type: "UPDATE_HEADER";
15
+ id: RawCoID;
16
+ header: boolean;
17
+ }
18
+ | {
19
+ type: "UPDATE_SESSION_COUNTER";
20
+ id: RawCoID;
21
+ sessionId: SessionID;
22
+ value: number;
23
+ }
24
+ | {
25
+ type: "SET";
26
+ id: RawCoID;
27
+ value: CoValueKnownState;
28
+ }
29
+ | {
30
+ type: "COMBINE_WITH";
31
+ id: RawCoID;
32
+ value: CoValueKnownState;
33
+ };
28
34
 
29
35
  export class PeerKnownStates {
30
- private coValues = new Map<RawCoID, CoValueKnownState>();
36
+ private coValues = new Map<RawCoID, CoValueKnownState>();
31
37
 
32
- private updateHeader(id: RawCoID, header: boolean) {
33
- const knownState = this.coValues.get(id) ?? emptyKnownState(id);
34
- knownState.header = header;
35
- this.coValues.set(id, knownState);
36
- }
38
+ private updateHeader(id: RawCoID, header: boolean) {
39
+ const knownState = this.coValues.get(id) ?? emptyKnownState(id);
40
+ knownState.header = header;
41
+ this.coValues.set(id, knownState);
42
+ }
37
43
 
38
- private combineWith(id: RawCoID, value: CoValueKnownState) {
39
- const knownState = this.coValues.get(id) ?? emptyKnownState(id);
40
- this.coValues.set(id, combinedKnownStates(knownState, value));
41
- }
44
+ private combineWith(id: RawCoID, value: CoValueKnownState) {
45
+ const knownState = this.coValues.get(id) ?? emptyKnownState(id);
46
+ this.coValues.set(id, combinedKnownStates(knownState, value));
47
+ }
42
48
 
43
- private updateSessionCounter(
44
- id: RawCoID,
45
- sessionId: SessionID,
46
- value: number
47
- ) {
48
- const knownState = this.coValues.get(id) ?? emptyKnownState(id);
49
- const currentValue = knownState.sessions[sessionId] || 0;
50
- knownState.sessions[sessionId] = Math.max(currentValue, value);
49
+ private updateSessionCounter(
50
+ id: RawCoID,
51
+ sessionId: SessionID,
52
+ value: number,
53
+ ) {
54
+ const knownState = this.coValues.get(id) ?? emptyKnownState(id);
55
+ const currentValue = knownState.sessions[sessionId] || 0;
56
+ knownState.sessions[sessionId] = Math.max(currentValue, value);
51
57
 
52
- this.coValues.set(id, knownState);
53
- }
58
+ this.coValues.set(id, knownState);
59
+ }
54
60
 
55
- get(id: RawCoID) {
56
- return this.coValues.get(id);
57
- }
61
+ get(id: RawCoID) {
62
+ return this.coValues.get(id);
63
+ }
58
64
 
59
- has(id: RawCoID) {
60
- return this.coValues.has(id);
61
- }
65
+ has(id: RawCoID) {
66
+ return this.coValues.has(id);
67
+ }
68
+
69
+ clone() {
70
+ const clone = new PeerKnownStates();
71
+ clone.coValues = new Map(this.coValues);
72
+ return clone;
73
+ }
62
74
 
63
- dispatch(action: PeerKnownStateActions) {
64
- switch (action.type) {
65
- case "UPDATE_HEADER":
66
- this.updateHeader(action.id, action.header);
67
- break;
68
- case "UPDATE_SESSION_COUNTER":
69
- this.updateSessionCounter(
70
- action.id,
71
- action.sessionId,
72
- action.value
73
- );
74
- break;
75
- case "SET":
76
- this.coValues.set(action.id, action.value);
77
- break;
78
- case "COMBINE_WITH":
79
- this.combineWith(action.id, action.value);
80
- break;
81
- case "SET_AS_EMPTY":
82
- this.coValues.set(action.id, emptyKnownState(action.id));
83
- break;
84
- }
85
-
86
- this.triggerUpdate(action.id);
75
+ dispatch(action: PeerKnownStateActions) {
76
+ switch (action.type) {
77
+ case "UPDATE_HEADER":
78
+ this.updateHeader(action.id, action.header);
79
+ break;
80
+ case "UPDATE_SESSION_COUNTER":
81
+ this.updateSessionCounter(action.id, action.sessionId, action.value);
82
+ break;
83
+ case "SET":
84
+ this.coValues.set(action.id, action.value);
85
+ break;
86
+ case "COMBINE_WITH":
87
+ this.combineWith(action.id, action.value);
88
+ break;
89
+ case "SET_AS_EMPTY":
90
+ this.coValues.set(action.id, emptyKnownState(action.id));
91
+ break;
87
92
  }
88
93
 
89
- listeners = new Set<(id: RawCoID, knownState: CoValueKnownState) => void>();
94
+ this.triggerUpdate(action.id);
95
+ }
90
96
 
91
- triggerUpdate(id: RawCoID) {
92
- this.trigger(id, this.coValues.get(id) ?? emptyKnownState(id));
93
- }
97
+ listeners = new Set<(id: RawCoID, knownState: CoValueKnownState) => void>();
98
+
99
+ triggerUpdate(id: RawCoID) {
100
+ this.trigger(id, this.coValues.get(id) ?? emptyKnownState(id));
101
+ }
94
102
 
95
- private trigger(id: RawCoID, knownState: CoValueKnownState) {
96
- for (const listener of this.listeners) {
97
- listener(id, knownState);
98
- }
103
+ private trigger(id: RawCoID, knownState: CoValueKnownState) {
104
+ for (const listener of this.listeners) {
105
+ listener(id, knownState);
99
106
  }
107
+ }
100
108
 
101
- subscribe(listener: (id: RawCoID, knownState: CoValueKnownState) => void) {
102
- this.listeners.add(listener);
109
+ subscribe(listener: (id: RawCoID, knownState: CoValueKnownState) => void) {
110
+ this.listeners.add(listener);
103
111
 
104
- return () => {
105
- this.listeners.delete(listener);
106
- };
107
- }
112
+ return () => {
113
+ this.listeners.delete(listener);
114
+ };
115
+ }
108
116
  }
package/src/PeerState.ts CHANGED
@@ -1,91 +1,110 @@
1
- import { RawCoID } from "./ids.js";
2
1
  import { PeerKnownStates } from "./PeerKnownStates.js";
3
- import { CO_VALUE_PRIORITY } from "./priority.js";
4
2
  import {
5
- PriorityBasedMessageQueue,
6
- QueueEntry,
3
+ PriorityBasedMessageQueue,
4
+ QueueEntry,
7
5
  } from "./PriorityBasedMessageQueue.js";
6
+ import { RawCoID } from "./ids.js";
7
+ import { CO_VALUE_PRIORITY } from "./priority.js";
8
8
  import { Peer, SyncMessage } from "./sync.js";
9
9
 
10
10
  export class PeerState {
11
- constructor(private peer: Peer) {}
12
-
13
- readonly optimisticKnownStates = new PeerKnownStates();
14
- readonly toldKnownState: Set<RawCoID> = new Set();
15
-
16
- get id() {
17
- return this.peer.id;
11
+ constructor(
12
+ private peer: Peer,
13
+ knownStates: PeerKnownStates | undefined,
14
+ ) {
15
+ this.optimisticKnownStates = knownStates?.clone() ?? new PeerKnownStates();
16
+ this.knownStates = knownStates?.clone() ?? new PeerKnownStates();
17
+ }
18
+
19
+ /**
20
+ * Here we to collect all the known states that a given peer has told us about.
21
+ *
22
+ * This can be used to safely track the sync state of a coValue in a given peer.
23
+ */
24
+ readonly knownStates: PeerKnownStates;
25
+
26
+ /**
27
+ * This one collects the known states "optimistically".
28
+ * We use it to keep track of the content we have sent to a given peer.
29
+ *
30
+ * The main difference with knownState is that this is updated when the content is sent to the peer without
31
+ * waiting for any acknowledgement from the peer.
32
+ */
33
+ readonly optimisticKnownStates: PeerKnownStates;
34
+ readonly toldKnownState: Set<RawCoID> = new Set();
35
+
36
+ get id() {
37
+ return this.peer.id;
38
+ }
39
+
40
+ get role() {
41
+ return this.peer.role;
42
+ }
43
+
44
+ get priority() {
45
+ return this.peer.priority;
46
+ }
47
+
48
+ get crashOnClose() {
49
+ return this.peer.crashOnClose;
50
+ }
51
+
52
+ isServerOrStoragePeer() {
53
+ return this.peer.role === "server" || this.peer.role === "storage";
54
+ }
55
+
56
+ /**
57
+ * We set as default priority HIGH to handle all the messages without a
58
+ * priority property as HIGH priority.
59
+ *
60
+ * This way we consider all the non-content messsages as HIGH priority.
61
+ */
62
+ private queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.HIGH);
63
+ private processing = false;
64
+ public closed = false;
65
+
66
+ async processQueue() {
67
+ if (this.processing) {
68
+ return;
18
69
  }
19
70
 
20
- get role() {
21
- return this.peer.role;
71
+ this.processing = true;
72
+
73
+ let entry: QueueEntry | undefined;
74
+ while ((entry = this.queue.pull())) {
75
+ // Awaiting the push to send one message at a time
76
+ // This way when the peer is "under pressure" we can enqueue all
77
+ // the coming messages and organize them by priority
78
+ await this.peer.outgoing
79
+ .push(entry.msg)
80
+ .then(entry.resolve)
81
+ .catch(entry.reject);
22
82
  }
23
83
 
24
- get priority() {
25
- return this.peer.priority;
26
- }
84
+ this.processing = false;
85
+ }
27
86
 
28
- get crashOnClose() {
29
- return this.peer.crashOnClose;
30
- }
87
+ pushOutgoingMessage(msg: SyncMessage) {
88
+ const promise = this.queue.push(msg);
31
89
 
32
- isServerOrStoragePeer() {
33
- return this.peer.role === "server" || this.peer.role === "storage";
34
- }
35
-
36
- /**
37
- * We set as default priority HIGH to handle all the messages without a
38
- * priority property as HIGH priority.
39
- *
40
- * This way we consider all the non-content messsages as HIGH priority.
41
- */
42
- private queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.HIGH);
43
- private processing = false;
44
- public closed = false;
45
-
46
- async processQueue() {
47
- if (this.processing) {
48
- return;
49
- }
50
-
51
- this.processing = true;
52
-
53
-
54
- let entry: QueueEntry | undefined;
55
- while ((entry = this.queue.pull())) {
56
- // Awaiting the push to send one message at a time
57
- // This way when the peer is "under pressure" we can enqueue all
58
- // the coming messages and organize them by priority
59
- await this.peer.outgoing
60
- .push(entry.msg)
61
- .then(entry.resolve)
62
- .catch(entry.reject);
63
- }
64
-
65
- this.processing = false;
66
- }
67
-
68
- pushOutgoingMessage(msg: SyncMessage) {
69
- const promise = this.queue.push(msg);
90
+ void this.processQueue();
70
91
 
71
- void this.processQueue();
92
+ return promise;
93
+ }
72
94
 
73
- return promise;
95
+ get incoming() {
96
+ if (this.closed) {
97
+ return (async function* () {
98
+ yield "Disconnected" as const;
99
+ })();
74
100
  }
75
101
 
76
- get incoming() {
77
- if (this.closed) {
78
- return (async function* () {
79
- yield "Disconnected" as const;
80
- })();
81
- }
102
+ return this.peer.incoming;
103
+ }
82
104
 
83
- return this.peer.incoming;
84
- }
85
-
86
- gracefulShutdown() {
87
- console.debug("Gracefully closing", this.id);
88
- this.peer.outgoing.close();
89
- this.closed = true;
90
- }
105
+ gracefulShutdown() {
106
+ console.debug("Gracefully closing", this.id);
107
+ this.peer.outgoing.close();
108
+ this.closed = true;
109
+ }
91
110
  }
@@ -2,76 +2,69 @@ import { CoValuePriority } from "./priority.js";
2
2
  import { SyncMessage } from "./sync.js";
3
3
 
4
4
  function promiseWithResolvers<R>() {
5
- let resolve = (_: R) => {};
6
- let reject = (_: unknown) => {};
5
+ let resolve = (_: R) => {};
6
+ let reject = (_: unknown) => {};
7
7
 
8
- const promise = new Promise<R>((_resolve, _reject) => {
9
- resolve = _resolve;
10
- reject = _reject;
11
- });
8
+ const promise = new Promise<R>((_resolve, _reject) => {
9
+ resolve = _resolve;
10
+ reject = _reject;
11
+ });
12
12
 
13
- return {
14
- promise,
15
- resolve,
16
- reject,
17
- };
13
+ return {
14
+ promise,
15
+ resolve,
16
+ reject,
17
+ };
18
18
  }
19
19
 
20
20
  export type QueueEntry = {
21
- msg: SyncMessage;
22
- promise: Promise<void>;
23
- resolve: () => void;
24
- reject: (_: unknown) => void;
21
+ msg: SyncMessage;
22
+ promise: Promise<void>;
23
+ resolve: () => void;
24
+ reject: (_: unknown) => void;
25
25
  };
26
26
 
27
27
  /**
28
28
  * Since we have a fixed range of priority values (0-7) we can create a fixed array of queues.
29
29
  */
30
- type Tuple<T, N extends number, A extends unknown[] = []> = A extends { length: N } ? A : Tuple<T, N, [...A, T]>;
30
+ type Tuple<T, N extends number, A extends unknown[] = []> = A extends {
31
+ length: N;
32
+ }
33
+ ? A
34
+ : Tuple<T, N, [...A, T]>;
31
35
  type QueueTuple = Tuple<QueueEntry[], 8>;
32
36
 
33
37
  export class PriorityBasedMessageQueue {
34
- private queues: QueueTuple = [
35
- [],
36
- [],
37
- [],
38
- [],
39
- [],
40
- [],
41
- [],
42
- [],
43
- ];
44
-
45
- private getQueue(priority: CoValuePriority) {
46
- return this.queues[priority];
47
- }
38
+ private queues: QueueTuple = [[], [], [], [], [], [], [], []];
48
39
 
49
- constructor(
50
- private defaultPriority: CoValuePriority,
51
- ) {}
40
+ private getQueue(priority: CoValuePriority) {
41
+ return this.queues[priority];
42
+ }
52
43
 
53
- public push(msg: SyncMessage) {
54
- const { promise, resolve, reject } = promiseWithResolvers<void>();
55
- const entry: QueueEntry = { msg, promise, resolve, reject };
44
+ constructor(private defaultPriority: CoValuePriority) {}
56
45
 
57
- if ("priority" in msg) {
58
- const queue = this.getQueue(msg.priority);
46
+ public push(msg: SyncMessage) {
47
+ const { promise, resolve, reject } = promiseWithResolvers<void>();
48
+ const entry: QueueEntry = { msg, promise, resolve, reject };
59
49
 
60
- queue?.push(entry);
61
- } else {
62
- this.getQueue(this.defaultPriority).push(entry);
63
- }
50
+ if ("priority" in msg) {
51
+ const queue = this.getQueue(msg.priority);
64
52
 
65
- return promise;
53
+ queue?.push(entry);
54
+ } else {
55
+ this.getQueue(this.defaultPriority).push(entry);
66
56
  }
67
57
 
68
- public pull() {
69
- const activeQueue = this.queues.find((queue) => queue.length > 0);
58
+ return promise;
59
+ }
70
60
 
71
- if (!activeQueue) {
72
- return undefined;
73
- }
61
+ public pull() {
62
+ const activeQueue = this.queues.find((queue) => queue.length > 0);
74
63
 
75
- return activeQueue.shift();
64
+ if (!activeQueue) {
65
+ return undefined;
76
66
  }
67
+
68
+ return activeQueue.shift();
69
+ }
77
70
  }
@@ -0,0 +1,124 @@
1
+ import { RawCoID } from "./ids.js";
2
+ import {
3
+ CoValueKnownState,
4
+ PeerID,
5
+ SyncManager,
6
+ emptyKnownState,
7
+ } from "./sync.js";
8
+
9
+ export class SyncStateSubscriptionManager {
10
+ constructor(private syncManager: SyncManager) {}
11
+
12
+ private listeners = new Set<
13
+ (
14
+ peerId: PeerID,
15
+ knownState: CoValueKnownState,
16
+ uploadCompleted: boolean,
17
+ ) => void
18
+ >();
19
+
20
+ private listenersByPeers = new Map<
21
+ PeerID,
22
+ Set<(knownState: CoValueKnownState, uploadCompleted: boolean) => void>
23
+ >();
24
+
25
+ subscribeToUpdates(
26
+ listener: (
27
+ peerId: PeerID,
28
+ knownState: CoValueKnownState,
29
+ uploadCompleted: boolean,
30
+ ) => void,
31
+ ) {
32
+ this.listeners.add(listener);
33
+
34
+ return () => {
35
+ this.listeners.delete(listener);
36
+ };
37
+ }
38
+
39
+ subscribeToPeerUpdates(
40
+ peerId: PeerID,
41
+ listener: (knownState: CoValueKnownState, uploadCompleted: boolean) => void,
42
+ ) {
43
+ const listeners = this.listenersByPeers.get(peerId) ?? new Set();
44
+
45
+ if (listeners.size === 0) {
46
+ this.listenersByPeers.set(peerId, listeners);
47
+ }
48
+
49
+ listeners.add(listener);
50
+
51
+ return () => {
52
+ listeners.delete(listener);
53
+ };
54
+ }
55
+
56
+ triggerUpdate(peerId: PeerID, id: RawCoID) {
57
+ const peer = this.syncManager.peers[peerId];
58
+
59
+ if (!peer) {
60
+ return;
61
+ }
62
+
63
+ const peerListeners = this.listenersByPeers.get(peer.id);
64
+
65
+ // If we don't have any active listeners do nothing
66
+ if (!peerListeners?.size && !this.listeners.size) {
67
+ return;
68
+ }
69
+
70
+ const knownState = peer.knownStates.get(id) ?? emptyKnownState(id);
71
+ const fullyUploadedIntoPeer = this.getIsCoValueFullyUploadedIntoPeer(
72
+ peerId,
73
+ id,
74
+ );
75
+
76
+ for (const listener of this.listeners) {
77
+ listener(peerId, knownState, fullyUploadedIntoPeer);
78
+ }
79
+
80
+ if (!peerListeners) return;
81
+
82
+ for (const listener of peerListeners) {
83
+ listener(knownState, fullyUploadedIntoPeer);
84
+ }
85
+ }
86
+
87
+ getIsCoValueFullyUploadedIntoPeer(peerId: PeerID, id: RawCoID) {
88
+ const peer = this.syncManager.peers[peerId];
89
+ const entry = this.syncManager.local.coValues[id];
90
+
91
+ if (!peer || !entry) {
92
+ return false;
93
+ }
94
+
95
+ if (entry.state.type !== "available") {
96
+ return false;
97
+ }
98
+
99
+ const coValue = entry.state.coValue;
100
+ const knownState = peer.knownStates.get(id);
101
+
102
+ if (!knownState) {
103
+ return false;
104
+ }
105
+
106
+ return getIsUploadCompleted(
107
+ coValue.knownState().sessions,
108
+ knownState.sessions,
109
+ );
110
+ }
111
+ }
112
+
113
+ function getIsUploadCompleted(
114
+ from: Record<string, number>,
115
+ to: Record<string, number>,
116
+ ) {
117
+ for (const sessionId of Object.keys(from)) {
118
+ if (from[sessionId] !== to[sessionId]) {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ return true;
124
+ }