cojson 0.15.7 → 0.15.9

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 (218) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/IncomingMessagesQueue.d.ts +27 -0
  4. package/dist/IncomingMessagesQueue.d.ts.map +1 -0
  5. package/dist/IncomingMessagesQueue.js +114 -0
  6. package/dist/IncomingMessagesQueue.js.map +1 -0
  7. package/dist/PeerState.d.ts +2 -10
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js +9 -90
  10. package/dist/PeerState.js.map +1 -1
  11. package/dist/PriorityBasedMessageQueue.d.ts +2 -1
  12. package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
  13. package/dist/PriorityBasedMessageQueue.js +9 -6
  14. package/dist/PriorityBasedMessageQueue.js.map +1 -1
  15. package/dist/SyncStateManager.d.ts +1 -0
  16. package/dist/SyncStateManager.d.ts.map +1 -1
  17. package/dist/SyncStateManager.js +1 -1
  18. package/dist/SyncStateManager.js.map +1 -1
  19. package/dist/coValue.d.ts +1 -1
  20. package/dist/coValueCore/coValueCore.d.ts +9 -17
  21. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  22. package/dist/coValueCore/coValueCore.js +75 -50
  23. package/dist/coValueCore/coValueCore.js.map +1 -1
  24. package/dist/coValueCore/verifiedState.d.ts +10 -3
  25. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  26. package/dist/coValueCore/verifiedState.js +73 -14
  27. package/dist/coValueCore/verifiedState.js.map +1 -1
  28. package/dist/coValues/coMap.d.ts +3 -3
  29. package/dist/coValues/coStream.d.ts +2 -2
  30. package/dist/coValues/group.d.ts +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +2 -4
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +19 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +23 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  39. package/dist/crypto/WasmCrypto.js +2 -1
  40. package/dist/crypto/WasmCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +18 -7
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +11 -8
  44. package/dist/exports.js.map +1 -1
  45. package/dist/localNode.d.ts +8 -2
  46. package/dist/localNode.d.ts.map +1 -1
  47. package/dist/localNode.js +19 -12
  48. package/dist/localNode.js.map +1 -1
  49. package/dist/storage/StoreQueue.d.ts +15 -0
  50. package/dist/storage/StoreQueue.d.ts.map +1 -0
  51. package/dist/storage/StoreQueue.js +35 -0
  52. package/dist/storage/StoreQueue.js.map +1 -0
  53. package/dist/storage/index.d.ts +6 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +6 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/knownState.d.ts +18 -0
  58. package/dist/storage/knownState.d.ts.map +1 -0
  59. package/dist/storage/knownState.js +63 -0
  60. package/dist/storage/knownState.js.map +1 -0
  61. package/dist/storage/sqlite/client.d.ts +37 -0
  62. package/dist/storage/sqlite/client.d.ts.map +1 -0
  63. package/dist/storage/sqlite/client.js +89 -0
  64. package/dist/storage/sqlite/client.js.map +1 -0
  65. package/dist/storage/sqlite/index.d.ts +5 -0
  66. package/dist/storage/sqlite/index.d.ts.map +1 -0
  67. package/dist/storage/sqlite/index.js +13 -0
  68. package/dist/storage/sqlite/index.js.map +1 -0
  69. package/dist/storage/sqlite/sqliteMigrations.d.ts +3 -0
  70. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -0
  71. package/dist/storage/sqlite/sqliteMigrations.js +44 -0
  72. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -0
  73. package/dist/storage/sqlite/types.d.ts +8 -0
  74. package/dist/storage/sqlite/types.d.ts.map +1 -0
  75. package/dist/storage/sqlite/types.js +2 -0
  76. package/dist/storage/sqlite/types.js.map +1 -0
  77. package/dist/storage/sqliteAsync/client.d.ts +37 -0
  78. package/dist/storage/sqliteAsync/client.d.ts.map +1 -0
  79. package/dist/storage/sqliteAsync/client.js +88 -0
  80. package/dist/storage/sqliteAsync/client.js.map +1 -0
  81. package/dist/storage/sqliteAsync/index.d.ts +6 -0
  82. package/dist/storage/sqliteAsync/index.d.ts.map +1 -0
  83. package/dist/storage/sqliteAsync/index.js +15 -0
  84. package/dist/storage/sqliteAsync/index.js.map +1 -0
  85. package/dist/storage/sqliteAsync/types.d.ts +9 -0
  86. package/dist/storage/sqliteAsync/types.d.ts.map +1 -0
  87. package/dist/storage/sqliteAsync/types.js +2 -0
  88. package/dist/storage/sqliteAsync/types.js.map +1 -0
  89. package/dist/storage/storageAsync.d.ts +22 -0
  90. package/dist/storage/storageAsync.d.ts.map +1 -0
  91. package/dist/storage/storageAsync.js +214 -0
  92. package/dist/storage/storageAsync.js.map +1 -0
  93. package/dist/storage/storageSync.d.ts +21 -0
  94. package/dist/storage/storageSync.d.ts.map +1 -0
  95. package/dist/storage/storageSync.js +206 -0
  96. package/dist/storage/storageSync.js.map +1 -0
  97. package/dist/storage/syncUtils.d.ts +13 -0
  98. package/dist/storage/syncUtils.d.ts.map +1 -0
  99. package/dist/storage/syncUtils.js +25 -0
  100. package/dist/storage/syncUtils.js.map +1 -0
  101. package/dist/storage/types.d.ts +82 -0
  102. package/dist/storage/types.d.ts.map +1 -0
  103. package/dist/storage/types.js +2 -0
  104. package/dist/storage/types.js.map +1 -0
  105. package/dist/streamUtils.d.ts +13 -9
  106. package/dist/streamUtils.d.ts.map +1 -1
  107. package/dist/streamUtils.js +46 -13
  108. package/dist/streamUtils.js.map +1 -1
  109. package/dist/sync.d.ts +22 -14
  110. package/dist/sync.d.ts.map +1 -1
  111. package/dist/sync.js +143 -125
  112. package/dist/sync.js.map +1 -1
  113. package/dist/tests/IncomingMessagesQueue.test.d.ts +2 -0
  114. package/dist/tests/IncomingMessagesQueue.test.d.ts.map +1 -0
  115. package/dist/tests/IncomingMessagesQueue.test.js +437 -0
  116. package/dist/tests/IncomingMessagesQueue.test.js.map +1 -0
  117. package/dist/tests/PeerState.test.js +6 -94
  118. package/dist/tests/PeerState.test.js.map +1 -1
  119. package/dist/tests/PriorityBasedMessageQueue.test.js +14 -14
  120. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
  121. package/dist/tests/StoreQueue.test.d.ts +2 -0
  122. package/dist/tests/StoreQueue.test.d.ts.map +1 -0
  123. package/dist/tests/StoreQueue.test.js +208 -0
  124. package/dist/tests/StoreQueue.test.js.map +1 -0
  125. package/dist/tests/SyncStateManager.test.js +3 -1
  126. package/dist/tests/SyncStateManager.test.js.map +1 -1
  127. package/dist/tests/account.test.js +9 -9
  128. package/dist/tests/account.test.js.map +1 -1
  129. package/dist/tests/coStream.test.js +1 -1
  130. package/dist/tests/coStream.test.js.map +1 -1
  131. package/dist/tests/coValueCore.test.js +208 -1
  132. package/dist/tests/coValueCore.test.js.map +1 -1
  133. package/dist/tests/coValueCoreLoadingState.test.js +2 -2
  134. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
  135. package/dist/tests/group.addMember.test.js.map +1 -1
  136. package/dist/tests/group.removeMember.test.js +1 -1
  137. package/dist/tests/group.removeMember.test.js.map +1 -1
  138. package/dist/tests/messagesTestUtils.js +1 -1
  139. package/dist/tests/messagesTestUtils.js.map +1 -1
  140. package/dist/tests/sync.auth.test.js +23 -15
  141. package/dist/tests/sync.auth.test.js.map +1 -1
  142. package/dist/tests/sync.invite.test.js +10 -16
  143. package/dist/tests/sync.invite.test.js.map +1 -1
  144. package/dist/tests/sync.load.test.js +52 -50
  145. package/dist/tests/sync.load.test.js.map +1 -1
  146. package/dist/tests/sync.mesh.test.js +173 -56
  147. package/dist/tests/sync.mesh.test.js.map +1 -1
  148. package/dist/tests/sync.peerReconciliation.test.js +42 -32
  149. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  150. package/dist/tests/sync.storage.test.js +162 -62
  151. package/dist/tests/sync.storage.test.js.map +1 -1
  152. package/dist/tests/sync.storageAsync.test.d.ts +2 -0
  153. package/dist/tests/sync.storageAsync.test.d.ts.map +1 -0
  154. package/dist/tests/sync.storageAsync.test.js +361 -0
  155. package/dist/tests/sync.storageAsync.test.js.map +1 -0
  156. package/dist/tests/sync.test.js +16 -21
  157. package/dist/tests/sync.test.js.map +1 -1
  158. package/dist/tests/sync.upload.test.js +28 -25
  159. package/dist/tests/sync.upload.test.js.map +1 -1
  160. package/dist/tests/testStorage.d.ts +12 -0
  161. package/dist/tests/testStorage.d.ts.map +1 -0
  162. package/dist/tests/testStorage.js +151 -0
  163. package/dist/tests/testStorage.js.map +1 -0
  164. package/dist/tests/testUtils.d.ts +20 -15
  165. package/dist/tests/testUtils.d.ts.map +1 -1
  166. package/dist/tests/testUtils.js +79 -45
  167. package/dist/tests/testUtils.js.map +1 -1
  168. package/package.json +2 -2
  169. package/src/IncomingMessagesQueue.ts +142 -0
  170. package/src/PeerState.ts +11 -110
  171. package/src/PriorityBasedMessageQueue.ts +13 -5
  172. package/src/SyncStateManager.ts +1 -1
  173. package/src/coValueCore/coValueCore.ts +100 -66
  174. package/src/coValueCore/verifiedState.ts +91 -21
  175. package/src/coValues/group.ts +2 -4
  176. package/src/config.ts +26 -0
  177. package/src/crypto/WasmCrypto.ts +3 -1
  178. package/src/exports.ts +20 -27
  179. package/src/localNode.ts +27 -12
  180. package/src/storage/StoreQueue.ts +56 -0
  181. package/src/storage/index.ts +5 -0
  182. package/src/storage/knownState.ts +88 -0
  183. package/src/storage/sqlite/client.ts +180 -0
  184. package/src/storage/sqlite/index.ts +19 -0
  185. package/src/storage/sqlite/sqliteMigrations.ts +44 -0
  186. package/src/storage/sqlite/types.ts +7 -0
  187. package/src/storage/sqliteAsync/client.ts +179 -0
  188. package/src/storage/sqliteAsync/index.ts +25 -0
  189. package/src/storage/sqliteAsync/types.ts +8 -0
  190. package/src/storage/storageAsync.ts +367 -0
  191. package/src/storage/storageSync.ts +343 -0
  192. package/src/storage/syncUtils.ts +50 -0
  193. package/src/storage/types.ts +162 -0
  194. package/src/streamUtils.ts +61 -19
  195. package/src/sync.ts +191 -160
  196. package/src/tests/IncomingMessagesQueue.test.ts +626 -0
  197. package/src/tests/PeerState.test.ts +6 -118
  198. package/src/tests/PriorityBasedMessageQueue.test.ts +18 -14
  199. package/src/tests/StoreQueue.test.ts +283 -0
  200. package/src/tests/SyncStateManager.test.ts +4 -1
  201. package/src/tests/account.test.ts +11 -12
  202. package/src/tests/coStream.test.ts +1 -3
  203. package/src/tests/coValueCore.test.ts +270 -1
  204. package/src/tests/coValueCoreLoadingState.test.ts +2 -2
  205. package/src/tests/group.addMember.test.ts +1 -0
  206. package/src/tests/group.removeMember.test.ts +2 -8
  207. package/src/tests/messagesTestUtils.ts +2 -2
  208. package/src/tests/sync.auth.test.ts +24 -14
  209. package/src/tests/sync.invite.test.ts +11 -17
  210. package/src/tests/sync.load.test.ts +53 -49
  211. package/src/tests/sync.mesh.test.ts +198 -56
  212. package/src/tests/sync.peerReconciliation.test.ts +44 -34
  213. package/src/tests/sync.storage.test.ts +231 -64
  214. package/src/tests/sync.storageAsync.test.ts +486 -0
  215. package/src/tests/sync.test.ts +17 -23
  216. package/src/tests/sync.upload.test.ts +29 -24
  217. package/src/tests/testStorage.ts +216 -0
  218. package/src/tests/testUtils.ts +89 -54
@@ -0,0 +1,626 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { IncomingMessagesQueue } from "../IncomingMessagesQueue.js";
3
+ import { PeerState } from "../PeerState.js";
4
+ import { ConnectedPeerChannel } from "../streamUtils.js";
5
+ import { Peer, SyncMessage } from "../sync.js";
6
+ import {
7
+ createTestMetricReader,
8
+ tearDownTestMetricReader,
9
+ } from "./testUtils.js";
10
+
11
+ // Mock performance.now for consistent timing tests
12
+ const mockPerformanceNow = vi.fn();
13
+ Object.defineProperty(global, "performance", {
14
+ value: {
15
+ now: mockPerformanceNow,
16
+ },
17
+ writable: true,
18
+ });
19
+
20
+ function createMockPeer(id: string): Peer {
21
+ return {
22
+ id,
23
+ role: "client",
24
+ incoming: new ConnectedPeerChannel(),
25
+ outgoing: new ConnectedPeerChannel(),
26
+ };
27
+ }
28
+
29
+ function createMockPeerState(
30
+ id: string,
31
+ role: "client" | "server" = "client",
32
+ ): PeerState {
33
+ const peer = createMockPeer(id);
34
+ peer.role = role;
35
+ return new PeerState(peer, undefined);
36
+ }
37
+
38
+ function createMockSyncMessage(
39
+ id: string,
40
+ action: "load" | "known" | "content" | "done" = "load",
41
+ ): SyncMessage {
42
+ if (action === "load" || action === "known") {
43
+ return {
44
+ action,
45
+ id: `co_z${id}`,
46
+ header: false,
47
+ sessions: {},
48
+ };
49
+ } else if (action === "content") {
50
+ return {
51
+ action: "content",
52
+ id: `co_z${id}`,
53
+ priority: 3, // MEDIUM priority
54
+ new: {},
55
+ };
56
+ } else {
57
+ return {
58
+ action: "done",
59
+ id: `co_z${id}`,
60
+ };
61
+ }
62
+ }
63
+
64
+ function setup() {
65
+ const metricReader = createTestMetricReader();
66
+ const queue = new IncomingMessagesQueue();
67
+ const peer1 = createMockPeerState("peer1");
68
+ const peer2 = createMockPeerState("peer2");
69
+
70
+ return { queue, peer1, peer2, metricReader };
71
+ }
72
+
73
+ beforeEach(() => {
74
+ mockPerformanceNow.mockReturnValue(0);
75
+ });
76
+
77
+ afterEach(() => {
78
+ tearDownTestMetricReader();
79
+ });
80
+
81
+ describe("IncomingMessagesQueue", () => {
82
+ describe("constructor", () => {
83
+ test("should initialize with empty state", () => {
84
+ const { queue } = setup();
85
+ expect(queue["queues"]).toEqual([]);
86
+ expect(queue.currentQueue).toBe(0);
87
+ expect(queue.processing).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe("push", () => {
92
+ test("should add message to new peer queue", () => {
93
+ const { queue, peer1 } = setup();
94
+ const msg = createMockSyncMessage("test");
95
+ queue.push(msg, peer1);
96
+
97
+ expect(queue["queues"].length).toBe(1);
98
+ expect(queue["queues"][0]?.[1]).toBe(peer1);
99
+ expect(queue["peerToQueue"].has(peer1)).toBe(true);
100
+ });
101
+
102
+ test("should add message to existing peer queue", () => {
103
+ const { queue, peer1 } = setup();
104
+ const msg1 = createMockSyncMessage("test1");
105
+ const msg2 = createMockSyncMessage("test2");
106
+
107
+ queue.push(msg1, peer1);
108
+ queue.push(msg2, peer1);
109
+
110
+ expect(queue["queues"].length).toBe(1);
111
+ const peerQueue = queue["peerToQueue"].get(peer1);
112
+ expect(peerQueue).toBeDefined();
113
+ expect(peerQueue?.length).toBe(2);
114
+ });
115
+
116
+ test("should handle multiple peers", () => {
117
+ const { queue, peer1, peer2 } = setup();
118
+ const msg1 = createMockSyncMessage("test1");
119
+ const msg2 = createMockSyncMessage("test2");
120
+
121
+ queue.push(msg1, peer1);
122
+ queue.push(msg2, peer2);
123
+
124
+ expect(queue["queues"].length).toBe(2);
125
+ expect(queue["peerToQueue"].has(peer1)).toBe(true);
126
+ expect(queue["peerToQueue"].has(peer2)).toBe(true);
127
+ });
128
+ });
129
+
130
+ describe("pull", () => {
131
+ test("should return undefined for empty queue", () => {
132
+ const { queue } = setup();
133
+ expect(queue.pull()).toBeUndefined();
134
+ });
135
+
136
+ test("should pull message from first peer", () => {
137
+ const { queue, peer1 } = setup();
138
+ const msg = createMockSyncMessage("test");
139
+ queue.push(msg, peer1);
140
+
141
+ const result = queue.pull();
142
+ expect(result).toEqual({ msg, peer: peer1 });
143
+ });
144
+
145
+ test("should pull messages in round-robin order", () => {
146
+ const { queue, peer1, peer2 } = setup();
147
+ const msg1 = createMockSyncMessage("test1");
148
+ const msg2 = createMockSyncMessage("test2");
149
+ const msg3 = createMockSyncMessage("test3");
150
+
151
+ queue.push(msg1, peer1);
152
+ queue.push(msg2, peer1);
153
+ queue.push(msg3, peer2);
154
+
155
+ // First pull from peer1
156
+ expect(queue.pull()).toEqual({ msg: msg1, peer: peer1 });
157
+ // Second pull from peer2 (round-robin)
158
+ expect(queue.pull()).toEqual({ msg: msg3, peer: peer2 });
159
+ // Third pull from peer1 (back to first)
160
+ expect(queue.pull()).toEqual({ msg: msg2, peer: peer1 });
161
+ });
162
+
163
+ test("should remove peer when their queue becomes empty", () => {
164
+ const { queue, peer1 } = setup();
165
+ const msg = createMockSyncMessage("test");
166
+ queue.push(msg, peer1);
167
+
168
+ queue.pull();
169
+
170
+ expect(queue["queues"].length).toBe(0);
171
+ expect(queue["peerToQueue"].has(peer1)).toBe(false);
172
+ });
173
+
174
+ test("should not advance currentQueue when only one peer has messages", () => {
175
+ const { queue, peer1 } = setup();
176
+ const msg1 = createMockSyncMessage("test1");
177
+ const msg2 = createMockSyncMessage("test2");
178
+
179
+ queue.push(msg1, peer1);
180
+ queue.push(msg2, peer1);
181
+
182
+ queue.pull(); // Pull first message
183
+
184
+ expect(queue.currentQueue).toBe(0); // Only one queue, so stays at 0
185
+ expect(queue["queues"].length).toBe(1); // Peer still has messages
186
+ });
187
+
188
+ test("should reset currentQueue when it reaches queue length", () => {
189
+ const { queue, peer1, peer2 } = setup();
190
+ const msg1 = createMockSyncMessage("test1");
191
+ const msg2 = createMockSyncMessage("test2");
192
+
193
+ queue.push(msg1, peer1);
194
+ queue.push(msg2, peer2);
195
+
196
+ queue.pull(); // Pull from peer1, currentQueue becomes 1
197
+ queue.pull(); // Pull from peer2, currentQueue becomes 2, then resets to 0
198
+
199
+ expect(queue.currentQueue).toBe(0);
200
+ });
201
+
202
+ test("should handle currentQueue reset when queues are removed", () => {
203
+ const { queue, peer1, peer2 } = setup();
204
+ const msg1 = createMockSyncMessage("test1");
205
+ const msg2 = createMockSyncMessage("test2");
206
+
207
+ queue.push(msg1, peer1);
208
+ queue.push(msg2, peer2);
209
+
210
+ queue.pull(); // Pull from peer1, currentQueue becomes 1
211
+ queue.pull(); // Pull from peer2, currentQueue becomes 2, then resets to 0
212
+
213
+ // Add another message to peer1
214
+ const msg3 = createMockSyncMessage("test3");
215
+ queue.push(msg3, peer1);
216
+
217
+ // Should pull from peer1 again (currentQueue is 0)
218
+ expect(queue.pull()).toEqual({ msg: msg3, peer: peer1 });
219
+ });
220
+ });
221
+
222
+ describe("processQueue", () => {
223
+ test("should process all messages in queue", async () => {
224
+ const { queue, peer1, peer2 } = setup();
225
+ const msg1 = createMockSyncMessage("test1");
226
+ const msg2 = createMockSyncMessage("test2");
227
+ const msg3 = createMockSyncMessage("test3");
228
+
229
+ queue.push(msg1, peer1);
230
+ queue.push(msg2, peer1);
231
+ queue.push(msg3, peer2);
232
+
233
+ const processedMessages: Array<{ msg: SyncMessage; peer: PeerState }> =
234
+ [];
235
+
236
+ await queue.processQueue((msg, peer) => {
237
+ processedMessages.push({ msg, peer });
238
+ });
239
+
240
+ expect(processedMessages).toEqual([
241
+ { msg: msg1, peer: peer1 },
242
+ { msg: msg3, peer: peer2 },
243
+ { msg: msg2, peer: peer1 },
244
+ ]);
245
+ expect(queue.processing).toBe(false);
246
+ });
247
+
248
+ test("should set processing flag during execution", async () => {
249
+ const { queue, peer1 } = setup();
250
+ const msg = createMockSyncMessage("test");
251
+ queue.push(msg, peer1);
252
+
253
+ let processingFlagDuringExecution = false;
254
+ const processingPromise = queue.processQueue(() => {
255
+ processingFlagDuringExecution = queue.processing;
256
+ });
257
+
258
+ await processingPromise;
259
+ expect(processingFlagDuringExecution).toBe(true);
260
+ expect(queue.processing).toBe(false);
261
+ });
262
+
263
+ test("should handle empty queue", async () => {
264
+ const { queue } = setup();
265
+ const callback = vi.fn();
266
+
267
+ await queue.processQueue(callback);
268
+
269
+ expect(callback).not.toHaveBeenCalled();
270
+ expect(queue.processing).toBe(false);
271
+ });
272
+
273
+ test("should yield to event loop when processing takes too long", async () => {
274
+ const { queue, peer1 } = setup();
275
+ const msg1 = createMockSyncMessage("test1");
276
+ const msg2 = createMockSyncMessage("test2");
277
+
278
+ queue.push(msg1, peer1);
279
+ queue.push(msg2, peer1);
280
+
281
+ // Mock timing to simulate long processing
282
+ mockPerformanceNow
283
+ .mockReturnValueOnce(0) // Initial time
284
+ .mockReturnValueOnce(60); // After first message (60ms > 50ms threshold)
285
+
286
+ const setTimeoutSpy = vi.spyOn(global, "setTimeout");
287
+
288
+ await queue.processQueue(() => {
289
+ // Simulate some processing time
290
+ });
291
+
292
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 0);
293
+ });
294
+
295
+ test("should not yield to event loop when processing is fast", async () => {
296
+ const { queue, peer1 } = setup();
297
+ const msg = createMockSyncMessage("test");
298
+ queue.push(msg, peer1);
299
+
300
+ // Mock timing to simulate fast processing
301
+ mockPerformanceNow
302
+ .mockReturnValueOnce(0) // Initial time
303
+ .mockReturnValueOnce(30); // After message (30ms < 50ms threshold)
304
+
305
+ const setTimeoutSpy = vi.spyOn(global, "setTimeout");
306
+
307
+ await queue.processQueue(() => {
308
+ // Simulate some processing time
309
+ });
310
+
311
+ expect(setTimeoutSpy).not.toHaveBeenCalled();
312
+ });
313
+
314
+ test("should handle callback errors gracefully", async () => {
315
+ const { queue, peer1 } = setup();
316
+ const msg = createMockSyncMessage("test");
317
+ queue.push(msg, peer1);
318
+
319
+ const error = new Error("Callback error");
320
+
321
+ await queue.processQueue(() => {
322
+ throw error;
323
+ });
324
+
325
+ // The processing flag should be reset even when an error occurs
326
+ expect(queue.processing).toBe(false);
327
+ });
328
+
329
+ test("should process messages in correct round-robin order", async () => {
330
+ const { queue, peer1, peer2 } = setup();
331
+ const msg1 = createMockSyncMessage("test1");
332
+ const msg2 = createMockSyncMessage("test2");
333
+ const msg3 = createMockSyncMessage("test3");
334
+ const msg4 = createMockSyncMessage("test4");
335
+
336
+ queue.push(msg1, peer1);
337
+ queue.push(msg2, peer1);
338
+ queue.push(msg3, peer2);
339
+ queue.push(msg4, peer2);
340
+
341
+ const processedMessages: Array<{ msg: SyncMessage; peer: PeerState }> =
342
+ [];
343
+
344
+ await queue.processQueue((msg, peer) => {
345
+ processedMessages.push({ msg, peer });
346
+ });
347
+
348
+ // Should process in round-robin: peer1, peer2, peer1, peer2
349
+ expect(processedMessages).toEqual([
350
+ { msg: msg1, peer: peer1 },
351
+ { msg: msg3, peer: peer2 },
352
+ { msg: msg2, peer: peer1 },
353
+ { msg: msg4, peer: peer2 },
354
+ ]);
355
+ });
356
+ });
357
+
358
+ describe("edge cases", () => {
359
+ test("should handle peer with multiple messages correctly", () => {
360
+ const { queue, peer1 } = setup();
361
+ const msg1 = createMockSyncMessage("test1");
362
+ const msg2 = createMockSyncMessage("test2");
363
+ const msg3 = createMockSyncMessage("test3");
364
+
365
+ queue.push(msg1, peer1);
366
+ queue.push(msg2, peer1);
367
+ queue.push(msg3, peer1);
368
+
369
+ expect(queue.pull()).toEqual({ msg: msg1, peer: peer1 });
370
+ expect(queue["queues"].length).toBe(1); // Peer still has messages
371
+ expect(queue.currentQueue).toBe(0); // Only one queue, so stays at 0
372
+
373
+ expect(queue.pull()).toEqual({ msg: msg2, peer: peer1 });
374
+ expect(queue["queues"].length).toBe(1); // Peer still has messages
375
+ expect(queue.currentQueue).toBe(0); // Only one queue, so stays at 0
376
+
377
+ expect(queue.pull()).toEqual({ msg: msg3, peer: peer1 });
378
+ expect(queue["queues"].length).toBe(0); // Peer queue is now empty
379
+ expect(queue.currentQueue).toBe(0); // Reset to 0
380
+ });
381
+
382
+ test("should handle rapid push and pull operations", () => {
383
+ const { queue, peer1 } = setup();
384
+ const msg1 = createMockSyncMessage("test1");
385
+ const msg2 = createMockSyncMessage("test2");
386
+
387
+ queue.push(msg1, peer1);
388
+ expect(queue.pull()).toEqual({ msg: msg1, peer: peer1 });
389
+
390
+ queue.push(msg2, peer1);
391
+ expect(queue.pull()).toEqual({ msg: msg2, peer: peer1 });
392
+
393
+ expect(queue["queues"].length).toBe(0);
394
+ expect(queue["peerToQueue"].has(peer1)).toBe(false);
395
+ });
396
+
397
+ test("should handle different message types", () => {
398
+ const { queue, peer1, peer2 } = setup();
399
+ const loadMsg = createMockSyncMessage("load", "load");
400
+ const knownMsg = createMockSyncMessage("known", "known");
401
+ const contentMsg = createMockSyncMessage("content", "content");
402
+ const doneMsg = createMockSyncMessage("done", "done");
403
+
404
+ queue.push(loadMsg, peer1);
405
+ queue.push(knownMsg, peer1);
406
+ queue.push(contentMsg, peer2);
407
+ queue.push(doneMsg, peer2);
408
+
409
+ expect(queue.pull()).toEqual({ msg: loadMsg, peer: peer1 });
410
+ expect(queue.pull()).toEqual({ msg: contentMsg, peer: peer2 });
411
+ expect(queue.pull()).toEqual({ msg: knownMsg, peer: peer1 });
412
+ expect(queue.pull()).toEqual({ msg: doneMsg, peer: peer2 });
413
+ });
414
+ });
415
+
416
+ describe("concurrent operations", () => {
417
+ test("should prevent multiple concurrent processQueue calls", async () => {
418
+ const { queue, peer1 } = setup();
419
+ const msg = createMockSyncMessage("test");
420
+ queue.push(msg, peer1);
421
+
422
+ const firstProcessSpy = vi.fn();
423
+
424
+ const firstProcess = queue.processQueue((msg, peer) => {
425
+ firstProcessSpy(msg, peer);
426
+ });
427
+
428
+ const secondProcessSpy = vi.fn();
429
+
430
+ // Second process should not interfere
431
+ const secondProcess = queue.processQueue(() => {
432
+ secondProcessSpy();
433
+ });
434
+
435
+ await firstProcess;
436
+ await secondProcess;
437
+
438
+ expect(firstProcessSpy).toHaveBeenCalled();
439
+ expect(secondProcessSpy).not.toHaveBeenCalled();
440
+
441
+ expect(queue.processing).toBe(false);
442
+ });
443
+ });
444
+
445
+ describe("metrics", () => {
446
+ test("should increment push counter when pushing messages", async () => {
447
+ const { queue, peer1, metricReader } = setup();
448
+ const msg = createMockSyncMessage("test");
449
+
450
+ queue.push(msg, peer1);
451
+
452
+ const pushValue = await metricReader.getMetricValue(
453
+ "jazz.messagequeue.incoming.pushed",
454
+ { peerRole: "client" },
455
+ );
456
+
457
+ expect(pushValue).toBe(1);
458
+ });
459
+
460
+ test("should increment pull counter when pulling messages", async () => {
461
+ const { queue, peer1, metricReader } = setup();
462
+ const msg = createMockSyncMessage("test");
463
+
464
+ queue.push(msg, peer1);
465
+ queue.pull();
466
+
467
+ const pullValue = await metricReader.getMetricValue(
468
+ "jazz.messagequeue.incoming.pulled",
469
+ { peerRole: "client" },
470
+ );
471
+
472
+ expect(pullValue).toBe(1);
473
+ });
474
+
475
+ test("should track metrics for both client and server peers", async () => {
476
+ const { queue, peer1, metricReader } = setup();
477
+
478
+ // Create a server peer
479
+ const serverPeer = createMockPeerState("server-peer", "server");
480
+
481
+ const clientMsg = createMockSyncMessage("client-test");
482
+ const serverMsg = createMockSyncMessage("server-test");
483
+
484
+ queue.push(clientMsg, peer1); // client peer
485
+ queue.push(serverMsg, serverPeer); // server peer
486
+
487
+ const clientPushValue = await metricReader.getMetricValue(
488
+ "jazz.messagequeue.incoming.pushed",
489
+ { peerRole: "client" },
490
+ );
491
+
492
+ const serverPushValue = await metricReader.getMetricValue(
493
+ "jazz.messagequeue.incoming.pushed",
494
+ { peerRole: "server" },
495
+ );
496
+
497
+ expect(clientPushValue).toBe(1);
498
+ expect(serverPushValue).toBe(1);
499
+ });
500
+
501
+ test("should track pull metrics for both client and server peers", async () => {
502
+ const { queue, peer1, metricReader } = setup();
503
+
504
+ // Create a server peer
505
+ const serverPeer = createMockPeerState("server-peer", "server");
506
+
507
+ const clientMsg = createMockSyncMessage("client-test");
508
+ const serverMsg = createMockSyncMessage("server-test");
509
+
510
+ queue.push(clientMsg, peer1);
511
+ queue.push(serverMsg, serverPeer);
512
+
513
+ queue.pull(); // Pull client message
514
+ queue.pull(); // Pull server message
515
+
516
+ const clientPullValue = await metricReader.getMetricValue(
517
+ "jazz.messagequeue.incoming.pulled",
518
+ { peerRole: "client" },
519
+ );
520
+
521
+ const serverPullValue = await metricReader.getMetricValue(
522
+ "jazz.messagequeue.incoming.pulled",
523
+ { peerRole: "server" },
524
+ );
525
+
526
+ expect(clientPullValue).toBe(1);
527
+ expect(serverPullValue).toBe(1);
528
+ });
529
+
530
+ test("should not increment pull counter when queue is empty", async () => {
531
+ const { queue, metricReader } = setup();
532
+
533
+ queue.pull(); // Should return undefined
534
+
535
+ const pullValue = await metricReader.getMetricValue(
536
+ "jazz.messagequeue.incoming.pulled",
537
+ { peerRole: "client" },
538
+ );
539
+
540
+ expect(pullValue).toBe(0);
541
+ });
542
+
543
+ test("should track multiple pushes and pulls correctly", async () => {
544
+ const { queue, peer1, metricReader } = setup();
545
+ const msg1 = createMockSyncMessage("test1");
546
+ const msg2 = createMockSyncMessage("test2");
547
+ const msg3 = createMockSyncMessage("test3");
548
+
549
+ queue.push(msg1, peer1);
550
+ queue.push(msg2, peer1);
551
+ queue.push(msg3, peer1);
552
+
553
+ queue.pull(); // Pull first message
554
+ queue.pull(); // Pull second message
555
+
556
+ const pushValue = await metricReader.getMetricValue(
557
+ "jazz.messagequeue.incoming.pushed",
558
+ { peerRole: "client" },
559
+ );
560
+
561
+ const pullValue = await metricReader.getMetricValue(
562
+ "jazz.messagequeue.incoming.pulled",
563
+ { peerRole: "client" },
564
+ );
565
+
566
+ expect(pushValue).toBe(3);
567
+ expect(pullValue).toBe(2);
568
+ });
569
+
570
+ test("should initialize metrics with zero values for both peer roles", async () => {
571
+ const { metricReader } = setup();
572
+
573
+ // The constructor should initialize metrics with 0 values
574
+ const clientPushValue = await metricReader.getMetricValue(
575
+ "jazz.messagequeue.incoming.pushed",
576
+ { peerRole: "client" },
577
+ );
578
+
579
+ const serverPushValue = await metricReader.getMetricValue(
580
+ "jazz.messagequeue.incoming.pushed",
581
+ { peerRole: "server" },
582
+ );
583
+
584
+ const clientPullValue = await metricReader.getMetricValue(
585
+ "jazz.messagequeue.incoming.pulled",
586
+ { peerRole: "client" },
587
+ );
588
+
589
+ const serverPullValue = await metricReader.getMetricValue(
590
+ "jazz.messagequeue.incoming.pulled",
591
+ { peerRole: "server" },
592
+ );
593
+
594
+ expect(clientPushValue).toBe(0);
595
+ expect(serverPushValue).toBe(0);
596
+ expect(clientPullValue).toBe(0);
597
+ expect(serverPullValue).toBe(0);
598
+ });
599
+
600
+ test("should track metrics during processQueue execution", async () => {
601
+ const { queue, peer1, metricReader } = setup();
602
+ const msg1 = createMockSyncMessage("test1");
603
+ const msg2 = createMockSyncMessage("test2");
604
+
605
+ queue.push(msg1, peer1);
606
+ queue.push(msg2, peer1);
607
+
608
+ await queue.processQueue(() => {
609
+ // Process messages
610
+ });
611
+
612
+ const pushValue = await metricReader.getMetricValue(
613
+ "jazz.messagequeue.incoming.pushed",
614
+ { peerRole: "client" },
615
+ );
616
+
617
+ const pullValue = await metricReader.getMetricValue(
618
+ "jazz.messagequeue.incoming.pulled",
619
+ { peerRole: "client" },
620
+ );
621
+
622
+ expect(pushValue).toBe(2);
623
+ expect(pullValue).toBe(2);
624
+ });
625
+ });
626
+ });