cojson 0.19.22 → 0.20.1

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 (223) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +66 -0
  3. package/dist/PeerState.d.ts +6 -1
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +18 -3
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/coValueContentMessage.d.ts +0 -2
  8. package/dist/coValueContentMessage.d.ts.map +1 -1
  9. package/dist/coValueContentMessage.js +0 -8
  10. package/dist/coValueContentMessage.js.map +1 -1
  11. package/dist/coValueCore/SessionMap.d.ts +4 -2
  12. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  13. package/dist/coValueCore/SessionMap.js +30 -0
  14. package/dist/coValueCore/SessionMap.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +70 -5
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +302 -31
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +6 -1
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +9 -0
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coList.d.ts +4 -2
  24. package/dist/coValues/coList.d.ts.map +1 -1
  25. package/dist/coValues/coList.js +3 -0
  26. package/dist/coValues/coList.js.map +1 -1
  27. package/dist/coValues/group.d.ts.map +1 -1
  28. package/dist/coValues/group.js +3 -6
  29. package/dist/coValues/group.js.map +1 -1
  30. package/dist/config.d.ts +2 -8
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +4 -12
  33. package/dist/config.js.map +1 -1
  34. package/dist/crypto/NapiCrypto.d.ts +1 -2
  35. package/dist/crypto/NapiCrypto.d.ts.map +1 -1
  36. package/dist/crypto/NapiCrypto.js +19 -4
  37. package/dist/crypto/NapiCrypto.js.map +1 -1
  38. package/dist/crypto/RNCrypto.d.ts.map +1 -1
  39. package/dist/crypto/RNCrypto.js +19 -4
  40. package/dist/crypto/RNCrypto.js.map +1 -1
  41. package/dist/crypto/WasmCrypto.d.ts +11 -4
  42. package/dist/crypto/WasmCrypto.d.ts.map +1 -1
  43. package/dist/crypto/WasmCrypto.js +52 -10
  44. package/dist/crypto/WasmCrypto.js.map +1 -1
  45. package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
  46. package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
  47. package/dist/crypto/WasmCryptoEdge.js +4 -1
  48. package/dist/crypto/WasmCryptoEdge.js.map +1 -1
  49. package/dist/crypto/crypto.d.ts +3 -3
  50. package/dist/crypto/crypto.d.ts.map +1 -1
  51. package/dist/crypto/crypto.js +6 -1
  52. package/dist/crypto/crypto.js.map +1 -1
  53. package/dist/exports.d.ts +5 -5
  54. package/dist/exports.d.ts.map +1 -1
  55. package/dist/exports.js +4 -3
  56. package/dist/exports.js.map +1 -1
  57. package/dist/ids.d.ts +4 -1
  58. package/dist/ids.d.ts.map +1 -1
  59. package/dist/ids.js +4 -0
  60. package/dist/ids.js.map +1 -1
  61. package/dist/knownState.d.ts +2 -0
  62. package/dist/knownState.d.ts.map +1 -1
  63. package/dist/localNode.d.ts +12 -0
  64. package/dist/localNode.d.ts.map +1 -1
  65. package/dist/localNode.js +14 -0
  66. package/dist/localNode.js.map +1 -1
  67. package/dist/platformUtils.d.ts +3 -0
  68. package/dist/platformUtils.d.ts.map +1 -0
  69. package/dist/platformUtils.js +24 -0
  70. package/dist/platformUtils.js.map +1 -0
  71. package/dist/queue/LinkedList.d.ts +9 -3
  72. package/dist/queue/LinkedList.d.ts.map +1 -1
  73. package/dist/queue/LinkedList.js +30 -1
  74. package/dist/queue/LinkedList.js.map +1 -1
  75. package/dist/queue/OutgoingLoadQueue.d.ts +95 -0
  76. package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -0
  77. package/dist/queue/OutgoingLoadQueue.js +240 -0
  78. package/dist/queue/OutgoingLoadQueue.js.map +1 -0
  79. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
  80. package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
  81. package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
  82. package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
  83. package/dist/storage/sqlite/client.d.ts +3 -0
  84. package/dist/storage/sqlite/client.d.ts.map +1 -1
  85. package/dist/storage/sqlite/client.js +44 -0
  86. package/dist/storage/sqlite/client.js.map +1 -1
  87. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
  88. package/dist/storage/sqlite/sqliteMigrations.js +7 -0
  89. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
  90. package/dist/storage/sqliteAsync/client.d.ts +3 -0
  91. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  92. package/dist/storage/sqliteAsync/client.js +42 -0
  93. package/dist/storage/sqliteAsync/client.js.map +1 -1
  94. package/dist/storage/storageAsync.d.ts +7 -0
  95. package/dist/storage/storageAsync.d.ts.map +1 -1
  96. package/dist/storage/storageAsync.js +48 -0
  97. package/dist/storage/storageAsync.js.map +1 -1
  98. package/dist/storage/storageSync.d.ts +6 -0
  99. package/dist/storage/storageSync.d.ts.map +1 -1
  100. package/dist/storage/storageSync.js +42 -0
  101. package/dist/storage/storageSync.js.map +1 -1
  102. package/dist/storage/types.d.ts +59 -0
  103. package/dist/storage/types.d.ts.map +1 -1
  104. package/dist/storage/types.js +12 -1
  105. package/dist/storage/types.js.map +1 -1
  106. package/dist/sync.d.ts.map +1 -1
  107. package/dist/sync.js +66 -43
  108. package/dist/sync.js.map +1 -1
  109. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
  110. package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
  111. package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
  112. package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
  113. package/dist/tests/GarbageCollector.test.js +5 -6
  114. package/dist/tests/GarbageCollector.test.js.map +1 -1
  115. package/dist/tests/LinkedList.test.js +90 -0
  116. package/dist/tests/LinkedList.test.js.map +1 -1
  117. package/dist/tests/OutgoingLoadQueue.test.d.ts +2 -0
  118. package/dist/tests/OutgoingLoadQueue.test.d.ts.map +1 -0
  119. package/dist/tests/OutgoingLoadQueue.test.js +814 -0
  120. package/dist/tests/OutgoingLoadQueue.test.js.map +1 -0
  121. package/dist/tests/StorageApiAsync.test.js +484 -152
  122. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  123. package/dist/tests/StorageApiSync.test.js +505 -136
  124. package/dist/tests/StorageApiSync.test.js.map +1 -1
  125. package/dist/tests/WasmCrypto.test.js +6 -3
  126. package/dist/tests/WasmCrypto.test.js.map +1 -1
  127. package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
  128. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  129. package/dist/tests/coValueCore.test.js +34 -13
  130. package/dist/tests/coValueCore.test.js.map +1 -1
  131. package/dist/tests/coreWasm.test.js +127 -4
  132. package/dist/tests/coreWasm.test.js.map +1 -1
  133. package/dist/tests/crypto.test.js +89 -93
  134. package/dist/tests/crypto.test.js.map +1 -1
  135. package/dist/tests/deleteCoValue.test.d.ts +2 -0
  136. package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
  137. package/dist/tests/deleteCoValue.test.js +313 -0
  138. package/dist/tests/deleteCoValue.test.js.map +1 -0
  139. package/dist/tests/group.removeMember.test.js +18 -30
  140. package/dist/tests/group.removeMember.test.js.map +1 -1
  141. package/dist/tests/knownState.lazyLoading.test.js +3 -0
  142. package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
  143. package/dist/tests/sync.concurrentLoad.test.d.ts +2 -0
  144. package/dist/tests/sync.concurrentLoad.test.d.ts.map +1 -0
  145. package/dist/tests/sync.concurrentLoad.test.js +481 -0
  146. package/dist/tests/sync.concurrentLoad.test.js.map +1 -0
  147. package/dist/tests/sync.deleted.test.d.ts +2 -0
  148. package/dist/tests/sync.deleted.test.d.ts.map +1 -0
  149. package/dist/tests/sync.deleted.test.js +214 -0
  150. package/dist/tests/sync.deleted.test.js.map +1 -0
  151. package/dist/tests/sync.mesh.test.js +3 -2
  152. package/dist/tests/sync.mesh.test.js.map +1 -1
  153. package/dist/tests/sync.storage.test.js +4 -3
  154. package/dist/tests/sync.storage.test.js.map +1 -1
  155. package/dist/tests/sync.test.js +3 -2
  156. package/dist/tests/sync.test.js.map +1 -1
  157. package/dist/tests/testStorage.d.ts +3 -0
  158. package/dist/tests/testStorage.d.ts.map +1 -1
  159. package/dist/tests/testStorage.js +17 -1
  160. package/dist/tests/testStorage.js.map +1 -1
  161. package/dist/tests/testUtils.d.ts +7 -3
  162. package/dist/tests/testUtils.d.ts.map +1 -1
  163. package/dist/tests/testUtils.js +19 -4
  164. package/dist/tests/testUtils.js.map +1 -1
  165. package/package.json +6 -16
  166. package/src/PeerState.ts +26 -3
  167. package/src/coValueContentMessage.ts +0 -14
  168. package/src/coValueCore/SessionMap.ts +43 -1
  169. package/src/coValueCore/coValueCore.ts +415 -27
  170. package/src/coValueCore/verifiedState.ts +26 -3
  171. package/src/coValues/coList.ts +9 -3
  172. package/src/coValues/group.ts +5 -6
  173. package/src/config.ts +4 -13
  174. package/src/crypto/NapiCrypto.ts +29 -13
  175. package/src/crypto/RNCrypto.ts +29 -11
  176. package/src/crypto/WasmCrypto.ts +67 -20
  177. package/src/crypto/WasmCryptoEdge.ts +5 -1
  178. package/src/crypto/crypto.ts +16 -4
  179. package/src/exports.ts +4 -2
  180. package/src/ids.ts +11 -1
  181. package/src/localNode.ts +15 -0
  182. package/src/platformUtils.ts +26 -0
  183. package/src/queue/LinkedList.ts +34 -4
  184. package/src/queue/OutgoingLoadQueue.ts +307 -0
  185. package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
  186. package/src/storage/sqlite/client.ts +77 -0
  187. package/src/storage/sqlite/sqliteMigrations.ts +7 -0
  188. package/src/storage/sqliteAsync/client.ts +75 -0
  189. package/src/storage/storageAsync.ts +62 -0
  190. package/src/storage/storageSync.ts +58 -0
  191. package/src/storage/types.ts +69 -0
  192. package/src/sync.ts +78 -46
  193. package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
  194. package/src/tests/GarbageCollector.test.ts +6 -10
  195. package/src/tests/LinkedList.test.ts +111 -0
  196. package/src/tests/OutgoingLoadQueue.test.ts +1129 -0
  197. package/src/tests/StorageApiAsync.test.ts +572 -162
  198. package/src/tests/StorageApiSync.test.ts +580 -143
  199. package/src/tests/WasmCrypto.test.ts +8 -3
  200. package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
  201. package/src/tests/coValueCore.test.ts +49 -14
  202. package/src/tests/coreWasm.test.ts +319 -10
  203. package/src/tests/crypto.test.ts +141 -150
  204. package/src/tests/deleteCoValue.test.ts +528 -0
  205. package/src/tests/group.removeMember.test.ts +35 -35
  206. package/src/tests/knownState.lazyLoading.test.ts +6 -0
  207. package/src/tests/sync.concurrentLoad.test.ts +650 -0
  208. package/src/tests/sync.deleted.test.ts +294 -0
  209. package/src/tests/sync.mesh.test.ts +5 -2
  210. package/src/tests/sync.storage.test.ts +6 -3
  211. package/src/tests/sync.test.ts +5 -2
  212. package/src/tests/testStorage.ts +31 -2
  213. package/src/tests/testUtils.ts +31 -10
  214. package/dist/crypto/PureJSCrypto.d.ts +0 -77
  215. package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
  216. package/dist/crypto/PureJSCrypto.js +0 -236
  217. package/dist/crypto/PureJSCrypto.js.map +0 -1
  218. package/dist/tests/PureJSCrypto.test.d.ts +0 -2
  219. package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
  220. package/dist/tests/PureJSCrypto.test.js +0 -145
  221. package/dist/tests/PureJSCrypto.test.js.map +0 -1
  222. package/src/crypto/PureJSCrypto.ts +0 -429
  223. package/src/tests/PureJSCrypto.test.ts +0 -217
@@ -0,0 +1,1129 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import {
3
+ CO_VALUE_LOADING_CONFIG,
4
+ setMaxInFlightLoadsPerPeer,
5
+ } from "../config.js";
6
+ import { OutgoingLoadQueue } from "../queue/OutgoingLoadQueue.js";
7
+ import type { PeerID } from "../sync.js";
8
+ import { createTestNode } from "./testUtils.js";
9
+
10
+ const TEST_PEER_ID = "test-peer" as PeerID;
11
+
12
+ // Store original config values
13
+ let originalMaxInFlightLoads: number;
14
+ let originalTimeout: number;
15
+
16
+ beforeEach(() => {
17
+ originalMaxInFlightLoads =
18
+ CO_VALUE_LOADING_CONFIG.MAX_IN_FLIGHT_LOADS_PER_PEER;
19
+ originalTimeout = CO_VALUE_LOADING_CONFIG.TIMEOUT;
20
+ });
21
+
22
+ afterEach(() => {
23
+ // Restore original config
24
+ setMaxInFlightLoadsPerPeer(originalMaxInFlightLoads);
25
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = originalTimeout;
26
+ vi.useRealTimers();
27
+ });
28
+
29
+ describe("OutgoingLoadQueue", () => {
30
+ describe("basic enqueue behavior", () => {
31
+ test("should call sendCallback immediately when queue has capacity", () => {
32
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
33
+ const node = createTestNode();
34
+ const group = node.createGroup();
35
+ const map = group.createMap();
36
+
37
+ let callbackCalled = false;
38
+ queue.enqueue(map.core, () => {
39
+ callbackCalled = true;
40
+ });
41
+
42
+ expect(callbackCalled).toBe(true);
43
+ expect(queue.inFlightCount).toBe(1);
44
+ });
45
+
46
+ test("should track sent request in inFlightLoads", () => {
47
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
48
+ const node = createTestNode();
49
+ const group = node.createGroup();
50
+ const map = group.createMap();
51
+
52
+ queue.enqueue(map.core, () => {});
53
+
54
+ expect(queue.inFlightCount).toBe(1);
55
+ });
56
+ });
57
+
58
+ describe("FIFO ordering", () => {
59
+ test("should maintain FIFO order for pending requests", () => {
60
+ setMaxInFlightLoadsPerPeer(1);
61
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
62
+ const node = createTestNode();
63
+ const group = node.createGroup();
64
+
65
+ // Block the queue first
66
+ const blockerMap = group.createMap();
67
+ queue.enqueue(blockerMap.core, () => {});
68
+
69
+ // Enqueue multiple CoValues
70
+ const map1 = group.createMap();
71
+ const map2 = group.createMap();
72
+ const map3 = group.createMap();
73
+
74
+ const order: string[] = [];
75
+
76
+ queue.enqueue(map1.core, () => order.push("map1"));
77
+ queue.enqueue(map2.core, () => order.push("map2"));
78
+ queue.enqueue(map3.core, () => order.push("map3"));
79
+
80
+ // Complete requests one by one
81
+ queue.trackComplete(blockerMap.core);
82
+ queue.trackComplete(map1.core);
83
+ queue.trackComplete(map2.core);
84
+
85
+ expect(order).toEqual(["map1", "map2", "map3"]);
86
+ });
87
+ });
88
+
89
+ describe("throttling", () => {
90
+ test("should queue requests when at capacity limit", () => {
91
+ setMaxInFlightLoadsPerPeer(2);
92
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
93
+ const node = createTestNode();
94
+ const group = node.createGroup();
95
+
96
+ const map1 = group.createMap();
97
+ const map2 = group.createMap();
98
+ const map3 = group.createMap();
99
+
100
+ let callback3Called = false;
101
+
102
+ queue.enqueue(map1.core, () => {});
103
+ queue.enqueue(map2.core, () => {});
104
+ queue.enqueue(map3.core, () => {
105
+ callback3Called = true;
106
+ });
107
+
108
+ // First two should be in flight
109
+ expect(queue.inFlightCount).toBe(2);
110
+ // Third should be pending
111
+ expect(queue.pendingCount).toBe(1);
112
+ expect(callback3Called).toBe(false);
113
+ });
114
+
115
+ test("should process queued requests when a slot becomes available", () => {
116
+ setMaxInFlightLoadsPerPeer(2);
117
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
118
+ const node = createTestNode();
119
+ const group = node.createGroup();
120
+
121
+ const map1 = group.createMap();
122
+ const map2 = group.createMap();
123
+ const map3 = group.createMap();
124
+
125
+ let callback3Called = false;
126
+
127
+ queue.enqueue(map1.core, () => {});
128
+ queue.enqueue(map2.core, () => {});
129
+ queue.enqueue(map3.core, () => {
130
+ callback3Called = true;
131
+ });
132
+
133
+ expect(callback3Called).toBe(false);
134
+
135
+ // Complete one request
136
+ queue.trackComplete(map1.core);
137
+
138
+ // Third should now be processed
139
+ expect(callback3Called).toBe(true);
140
+ expect(queue.inFlightCount).toBe(2);
141
+ expect(queue.pendingCount).toBe(0);
142
+ });
143
+ });
144
+
145
+ describe("request deduplication", () => {
146
+ test("should skip duplicate enqueue while pending", () => {
147
+ setMaxInFlightLoadsPerPeer(1);
148
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
149
+ const node = createTestNode();
150
+ const group = node.createGroup();
151
+
152
+ const blockerMap = group.createMap();
153
+ const targetMap = group.createMap();
154
+
155
+ let targetCallbackCount = 0;
156
+ let duplicateCallbackCount = 0;
157
+
158
+ queue.enqueue(blockerMap.core, () => {});
159
+
160
+ queue.enqueue(targetMap.core, () => {
161
+ targetCallbackCount += 1;
162
+ });
163
+ queue.enqueue(targetMap.core, () => {
164
+ duplicateCallbackCount += 1;
165
+ });
166
+
167
+ expect(queue.pendingCount).toBe(1);
168
+ expect(targetCallbackCount).toBe(0);
169
+ expect(duplicateCallbackCount).toBe(0);
170
+
171
+ queue.trackComplete(blockerMap.core);
172
+
173
+ expect(targetCallbackCount).toBe(1);
174
+ expect(duplicateCallbackCount).toBe(0);
175
+ });
176
+
177
+ test("should skip duplicate enqueue while in-flight", () => {
178
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
179
+ const node = createTestNode();
180
+ const group = node.createGroup();
181
+
182
+ const map = group.createMap();
183
+ let duplicateCallbackCount = 0;
184
+
185
+ queue.enqueue(map.core, () => {});
186
+ queue.enqueue(map.core, () => {
187
+ duplicateCallbackCount += 1;
188
+ });
189
+
190
+ expect(queue.inFlightCount).toBe(1);
191
+ expect(duplicateCallbackCount).toBe(0);
192
+ });
193
+
194
+ test("should skip duplicate enqueue for same CoValue ID", () => {
195
+ setMaxInFlightLoadsPerPeer(1);
196
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
197
+ const node = createTestNode();
198
+ const otherNode = createTestNode();
199
+ const group = node.createGroup();
200
+
201
+ const availableMap = group.createMap();
202
+ const sameIdCoValue = otherNode.getCoValue(availableMap.id);
203
+
204
+ queue.enqueue(availableMap.core, () => {});
205
+ queue.trackComplete(availableMap.core);
206
+
207
+ // Block capacity so the next enqueue stays pending
208
+ const blockerMap = group.createMap();
209
+ queue.enqueue(blockerMap.core, () => {});
210
+
211
+ let duplicateCallbackCount = 0;
212
+ queue.enqueue(availableMap.core, () => {});
213
+ queue.enqueue(sameIdCoValue, () => {
214
+ duplicateCallbackCount += 1;
215
+ });
216
+
217
+ expect(queue.pendingCount).toBe(1);
218
+ expect(duplicateCallbackCount).toBe(0);
219
+ });
220
+ });
221
+
222
+ describe("timeout behavior", () => {
223
+ test("should mark CoValue as not found in peer after timeout", async () => {
224
+ vi.useFakeTimers();
225
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
226
+
227
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
228
+ const node = createTestNode();
229
+
230
+ // Get an unavailable CoValue
231
+ const coValue = node.getCoValue("co_zTestTimeoutCoValue0001" as any);
232
+
233
+ queue.enqueue(coValue, () => {});
234
+
235
+ expect(queue.inFlightCount).toBe(1);
236
+
237
+ // Advance time past the timeout
238
+ await vi.advanceTimersByTimeAsync(1001);
239
+
240
+ // Should be removed from in-flight
241
+ expect(queue.inFlightCount).toBe(0);
242
+
243
+ // Should be marked as not found
244
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
245
+ });
246
+
247
+ test("should free the queue slot and process pending requests on timeout", async () => {
248
+ vi.useFakeTimers();
249
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
250
+ setMaxInFlightLoadsPerPeer(1);
251
+
252
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
253
+ const node = createTestNode();
254
+ const group = node.createGroup();
255
+
256
+ const coValue1 = node.getCoValue("co_zTestTimeoutFree00000001" as any);
257
+ const map2 = group.createMap();
258
+
259
+ let callback2Called = false;
260
+
261
+ queue.enqueue(coValue1, () => {});
262
+ queue.enqueue(map2.core, () => {
263
+ callback2Called = true;
264
+ });
265
+
266
+ expect(queue.inFlightCount).toBe(1);
267
+ expect(callback2Called).toBe(false);
268
+
269
+ // Advance time past the timeout
270
+ await vi.advanceTimersByTimeAsync(1001);
271
+
272
+ // First should have timed out, second should be processed
273
+ expect(callback2Called).toBe(true);
274
+ expect(queue.inFlightCount).toBe(1);
275
+ });
276
+
277
+ test("should timeout each in-flight load independently", async () => {
278
+ vi.useFakeTimers();
279
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
280
+ setMaxInFlightLoadsPerPeer(3);
281
+
282
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
283
+ const node = createTestNode();
284
+
285
+ const coValue1 = node.getCoValue("co_zTestSingleTimer000001" as any);
286
+ const coValue2 = node.getCoValue("co_zTestSingleTimer000002" as any);
287
+ const coValue3 = node.getCoValue("co_zTestSingleTimer000003" as any);
288
+
289
+ queue.enqueue(coValue1, () => {});
290
+ await vi.advanceTimersByTimeAsync(100);
291
+ queue.enqueue(coValue2, () => {});
292
+ await vi.advanceTimersByTimeAsync(100);
293
+ queue.enqueue(coValue3, () => {});
294
+
295
+ expect(queue.inFlightCount).toBe(3);
296
+
297
+ // Advance time past the timeout
298
+ await vi.advanceTimersByTimeAsync(801);
299
+
300
+ // All three should have timed out
301
+ expect(queue.inFlightCount).toBe(2);
302
+
303
+ await vi.advanceTimersByTimeAsync(101);
304
+ expect(coValue1.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
305
+ expect(queue.inFlightCount).toBe(1);
306
+ await vi.advanceTimersByTimeAsync(101);
307
+ expect(coValue2.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
308
+ expect(queue.inFlightCount).toBe(0);
309
+ await vi.advanceTimersByTimeAsync(101);
310
+ expect(coValue3.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
311
+ expect(queue.inFlightCount).toBe(0);
312
+ });
313
+
314
+ test("should allow re-enqueue after timeout", async () => {
315
+ vi.useFakeTimers();
316
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
317
+ setMaxInFlightLoadsPerPeer(1);
318
+
319
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
320
+ const node = createTestNode();
321
+
322
+ const coValue = node.getCoValue("co_zTestTimeoutReenqueue0001" as any);
323
+
324
+ let secondCallbackCount = 0;
325
+
326
+ queue.enqueue(coValue, () => {});
327
+ await vi.advanceTimersByTimeAsync(1001);
328
+
329
+ expect(queue.inFlightCount).toBe(0);
330
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
331
+
332
+ queue.enqueue(coValue, () => {
333
+ secondCallbackCount += 1;
334
+ });
335
+
336
+ expect(queue.inFlightCount).toBe(1);
337
+ expect(secondCallbackCount).toBe(1);
338
+ });
339
+
340
+ test("should warn but not mark as unavailable when streaming CoValue times out", async () => {
341
+ vi.useFakeTimers();
342
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
343
+ setMaxInFlightLoadsPerPeer(1);
344
+
345
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
346
+ const node = createTestNode();
347
+ const group = node.createGroup();
348
+
349
+ const map = group.createMap();
350
+ map.set("key", "value");
351
+
352
+ // Mock isStreaming to return true (CoValue is available but still streaming)
353
+ vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
354
+
355
+ queue.enqueue(map.core, () => {});
356
+
357
+ expect(queue.inFlightCount).toBe(1);
358
+
359
+ // Advance time past the timeout
360
+ await vi.advanceTimersByTimeAsync(1001);
361
+
362
+ // Should be removed from in-flight (timeout clears the slot)
363
+ expect(queue.inFlightCount).toBe(0);
364
+
365
+ // But should NOT be marked as unavailable since it's available (just streaming)
366
+ expect(map.core.isAvailable()).toBe(true);
367
+ });
368
+ });
369
+
370
+ describe("trackComplete", () => {
371
+ test("should remove from inFlightLoads and allow next request to process", () => {
372
+ setMaxInFlightLoadsPerPeer(1);
373
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
374
+ const node = createTestNode();
375
+ const group = node.createGroup();
376
+
377
+ const map1 = group.createMap();
378
+ const map2 = group.createMap();
379
+
380
+ let callback2Called = false;
381
+
382
+ queue.enqueue(map1.core, () => {});
383
+ queue.enqueue(map2.core, () => {
384
+ callback2Called = true;
385
+ });
386
+
387
+ expect(queue.inFlightCount).toBe(1);
388
+ expect(callback2Called).toBe(false);
389
+
390
+ queue.trackComplete(map1.core);
391
+
392
+ expect(callback2Called).toBe(true);
393
+ expect(queue.inFlightCount).toBe(1);
394
+ });
395
+
396
+ test("should be a no-op for unknown CoValues", () => {
397
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
398
+ const node = createTestNode();
399
+ const group = node.createGroup();
400
+
401
+ const map1 = group.createMap();
402
+ const unknownCoValue = node.getCoValue(
403
+ "co_zTestUnknownCoValue001" as any,
404
+ );
405
+
406
+ queue.enqueue(map1.core, () => {});
407
+
408
+ expect(queue.inFlightCount).toBe(1);
409
+
410
+ // trackComplete on unknown CoValue should be a no-op
411
+ queue.trackComplete(unknownCoValue);
412
+
413
+ expect(queue.inFlightCount).toBe(1);
414
+ });
415
+
416
+ test("should allow re-enqueue after completion", () => {
417
+ setMaxInFlightLoadsPerPeer(1);
418
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
419
+ const node = createTestNode();
420
+ const group = node.createGroup();
421
+
422
+ const map = group.createMap();
423
+
424
+ let firstCallbackCount = 0;
425
+ let secondCallbackCount = 0;
426
+
427
+ queue.enqueue(map.core, () => {
428
+ firstCallbackCount += 1;
429
+ });
430
+
431
+ expect(queue.inFlightCount).toBe(1);
432
+ expect(firstCallbackCount).toBe(1);
433
+
434
+ queue.trackComplete(map.core);
435
+
436
+ queue.enqueue(map.core, () => {
437
+ secondCallbackCount += 1;
438
+ });
439
+
440
+ expect(queue.inFlightCount).toBe(1);
441
+ expect(secondCallbackCount).toBe(1);
442
+ });
443
+
444
+ test("should not complete if CoValue is streaming", () => {
445
+ setMaxInFlightLoadsPerPeer(1);
446
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
447
+ const node = createTestNode();
448
+ const group = node.createGroup();
449
+
450
+ const map1 = group.createMap();
451
+ const map2 = group.createMap();
452
+
453
+ // Mock isStreaming to return true
454
+ vi.spyOn(map1.core, "isStreaming").mockReturnValue(true);
455
+
456
+ let callback2Called = false;
457
+
458
+ queue.enqueue(map1.core, () => {});
459
+ queue.enqueue(map2.core, () => {
460
+ callback2Called = true;
461
+ });
462
+
463
+ expect(queue.inFlightCount).toBe(1);
464
+ expect(callback2Called).toBe(false);
465
+
466
+ // trackComplete should not complete because map1 is streaming
467
+ queue.trackComplete(map1.core);
468
+
469
+ // Should still be in-flight and pending should not be processed
470
+ expect(queue.inFlightCount).toBe(1);
471
+ expect(callback2Called).toBe(false);
472
+ expect(queue.pendingCount).toBe(1);
473
+
474
+ // Stop streaming
475
+ vi.spyOn(map1.core, "isStreaming").mockReturnValue(false);
476
+
477
+ // Now trackComplete should work
478
+ queue.trackComplete(map1.core);
479
+
480
+ expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
481
+ expect(callback2Called).toBe(true);
482
+ expect(queue.pendingCount).toBe(0);
483
+ });
484
+ });
485
+
486
+ describe("trackUpdate", () => {
487
+ test("should refresh timeout for in-flight streaming CoValue", async () => {
488
+ vi.useFakeTimers();
489
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
490
+ setMaxInFlightLoadsPerPeer(1);
491
+
492
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
493
+ const node = createTestNode();
494
+ const group = node.createGroup();
495
+
496
+ const map = group.createMap();
497
+ map.set("key", "value");
498
+
499
+ // Mock isStreaming to return true
500
+ vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
501
+
502
+ queue.enqueue(map.core, () => {});
503
+
504
+ expect(queue.inFlightCount).toBe(1);
505
+
506
+ // Advance time to just before timeout
507
+ await vi.advanceTimersByTimeAsync(900);
508
+
509
+ // Refresh the timeout (simulating receiving a chunk)
510
+ queue.trackUpdate(map.core);
511
+
512
+ // Advance another 200ms - would have timed out without the refresh
513
+ await vi.advanceTimersByTimeAsync(200);
514
+
515
+ // Should still be in-flight because timeout was refreshed
516
+ expect(queue.inFlightCount).toBe(1);
517
+
518
+ // Now advance past the new timeout
519
+ await vi.advanceTimersByTimeAsync(900);
520
+
521
+ // Should have timed out now
522
+ expect(queue.inFlightCount).toBe(0);
523
+ });
524
+
525
+ test("should be a no-op for unknown CoValues", () => {
526
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
527
+ const node = createTestNode();
528
+ const group = node.createGroup();
529
+
530
+ const map = group.createMap();
531
+ const unknownCoValue = node.getCoValue(
532
+ "co_zTestTrackUpdateUnknown01" as any,
533
+ );
534
+
535
+ queue.enqueue(map.core, () => {});
536
+
537
+ expect(queue.inFlightCount).toBe(1);
538
+
539
+ // trackUpdate on unknown CoValue should be a no-op
540
+ queue.trackUpdate(unknownCoValue);
541
+
542
+ // Should still have the same in-flight count
543
+ expect(queue.inFlightCount).toBe(1);
544
+ });
545
+ });
546
+
547
+ describe("immediate mode", () => {
548
+ test("should send immediately when mode is immediate even at capacity", () => {
549
+ setMaxInFlightLoadsPerPeer(1);
550
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
551
+ const node = createTestNode();
552
+ const group = node.createGroup();
553
+
554
+ const map1 = group.createMap();
555
+ const map2 = group.createMap();
556
+
557
+ let callback1Called = false;
558
+ let callback2Called = false;
559
+
560
+ // Fill the queue to capacity
561
+ queue.enqueue(map1.core, () => {
562
+ callback1Called = true;
563
+ });
564
+
565
+ expect(callback1Called).toBe(true);
566
+ expect(queue.inFlightCount).toBe(1);
567
+
568
+ // This should bypass the capacity limit with immediate mode
569
+ queue.enqueue(
570
+ map2.core,
571
+ () => {
572
+ callback2Called = true;
573
+ },
574
+ "immediate",
575
+ );
576
+
577
+ // Both should have been called even though capacity is 1
578
+ expect(callback2Called).toBe(true);
579
+ expect(queue.inFlightCount).toBe(2);
580
+ });
581
+
582
+ test("should still track immediate requests for timeout handling", async () => {
583
+ vi.useFakeTimers();
584
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
585
+ setMaxInFlightLoadsPerPeer(1);
586
+
587
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
588
+ const node = createTestNode();
589
+
590
+ const coValue = node.getCoValue("co_zTestOverflowTimeout01" as any);
591
+
592
+ queue.enqueue(coValue, () => {}, "immediate");
593
+
594
+ expect(queue.inFlightCount).toBe(1);
595
+
596
+ // Advance time past the timeout
597
+ await vi.advanceTimersByTimeAsync(1001);
598
+
599
+ // Should be removed from in-flight and marked as not found
600
+ expect(queue.inFlightCount).toBe(0);
601
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
602
+ });
603
+
604
+ test("should still deduplicate immediate requests", () => {
605
+ setMaxInFlightLoadsPerPeer(1);
606
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
607
+ const node = createTestNode();
608
+ const group = node.createGroup();
609
+
610
+ const map = group.createMap();
611
+
612
+ let callbackCount = 0;
613
+
614
+ queue.enqueue(
615
+ map.core,
616
+ () => {
617
+ callbackCount++;
618
+ },
619
+ "immediate",
620
+ );
621
+ queue.enqueue(
622
+ map.core,
623
+ () => {
624
+ callbackCount++;
625
+ },
626
+ "immediate",
627
+ );
628
+
629
+ // Should only be called once due to deduplication
630
+ expect(callbackCount).toBe(1);
631
+ expect(queue.inFlightCount).toBe(1);
632
+ });
633
+
634
+ test("should process pending requests when immediate request completes", () => {
635
+ setMaxInFlightLoadsPerPeer(1);
636
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
637
+ const node = createTestNode();
638
+ const group = node.createGroup();
639
+
640
+ const map1 = group.createMap();
641
+ const immediateMap = group.createMap();
642
+ const map2 = group.createMap();
643
+
644
+ let callback2Called = false;
645
+
646
+ // Fill the queue
647
+ queue.enqueue(map1.core, () => {});
648
+
649
+ // Add pending request
650
+ queue.enqueue(map2.core, () => {
651
+ callback2Called = true;
652
+ });
653
+
654
+ expect(callback2Called).toBe(false);
655
+ expect(queue.pendingCount).toBe(1);
656
+
657
+ // Add immediate request - this bypasses the queue but still counts as in-flight
658
+ queue.enqueue(immediateMap.core, () => {}, "immediate");
659
+
660
+ expect(queue.inFlightCount).toBe(2);
661
+
662
+ // Complete the immediate request
663
+ queue.trackComplete(immediateMap.core);
664
+
665
+ // Still at capacity because map1 is still in-flight
666
+ expect(callback2Called).toBe(false);
667
+ expect(queue.inFlightCount).toBe(1);
668
+
669
+ // Complete the regular request - now pending can be processed
670
+ queue.trackComplete(map1.core);
671
+
672
+ expect(callback2Called).toBe(true);
673
+ expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
674
+ });
675
+ });
676
+
677
+ describe("clear", () => {
678
+ test("should clear all in-flight loads and pending queues", () => {
679
+ setMaxInFlightLoadsPerPeer(2);
680
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
681
+ const node = createTestNode();
682
+ const group = node.createGroup();
683
+
684
+ const map1 = group.createMap();
685
+ const map2 = group.createMap();
686
+ const map3 = group.createMap();
687
+
688
+ queue.enqueue(map1.core, () => {});
689
+ queue.enqueue(map2.core, () => {});
690
+ queue.enqueue(map3.core, () => {});
691
+
692
+ expect(queue.inFlightCount).toBe(2);
693
+ expect(queue.pendingCount).toBe(1);
694
+
695
+ queue.clear();
696
+
697
+ expect(queue.inFlightCount).toBe(0);
698
+ expect(queue.pendingCount).toBe(0);
699
+ });
700
+
701
+ test("should cancel any pending timeout", async () => {
702
+ vi.useFakeTimers();
703
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
704
+
705
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
706
+ const node = createTestNode();
707
+
708
+ const coValue = node.getCoValue("co_zTestClearTimeout00001" as any);
709
+
710
+ queue.enqueue(coValue, () => {});
711
+
712
+ expect(queue.inFlightCount).toBe(1);
713
+
714
+ // Clear before timeout
715
+ queue.clear();
716
+
717
+ expect(queue.inFlightCount).toBe(0);
718
+
719
+ // Advance time past the timeout
720
+ await vi.advanceTimersByTimeAsync(1001);
721
+
722
+ // Should not have marked as not found since we cleared
723
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).not.toBe(
724
+ "unavailable",
725
+ );
726
+ });
727
+ });
728
+
729
+ describe("priority queue", () => {
730
+ test("should process high-priority requests before low-priority", () => {
731
+ setMaxInFlightLoadsPerPeer(1);
732
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
733
+ const node = createTestNode();
734
+ const group = node.createGroup();
735
+
736
+ // Block the queue first
737
+ const blockerMap = group.createMap();
738
+ queue.enqueue(blockerMap.core, () => {});
739
+
740
+ // Enqueue low-priority requests
741
+ const lowMap1 = group.createMap();
742
+ const lowMap2 = group.createMap();
743
+
744
+ // Enqueue high-priority request
745
+ const highMap = group.createMap();
746
+
747
+ const order: string[] = [];
748
+
749
+ // Add low-priority first
750
+ queue.enqueue(lowMap1.core, () => order.push("low1"), "low-priority");
751
+ queue.enqueue(lowMap2.core, () => order.push("low2"), "low-priority");
752
+ // Add high-priority after
753
+ queue.enqueue(highMap.core, () => order.push("high"));
754
+
755
+ expect(queue.pendingCount).toBe(3);
756
+ expect(queue.highPriorityPendingCount).toBe(1);
757
+ expect(queue.lowPriorityPendingCount).toBe(2);
758
+
759
+ // Complete the blocker and process requests one by one
760
+ queue.trackComplete(blockerMap.core);
761
+ expect(order).toEqual(["high"]);
762
+
763
+ queue.trackComplete(highMap.core);
764
+ expect(order).toEqual(["high", "low1"]);
765
+
766
+ queue.trackComplete(lowMap1.core);
767
+ expect(order).toEqual(["high", "low1", "low2"]);
768
+ });
769
+
770
+ test("should add low-priority request to low-priority queue", () => {
771
+ setMaxInFlightLoadsPerPeer(1);
772
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
773
+ const node = createTestNode();
774
+ const group = node.createGroup();
775
+
776
+ // Block the queue
777
+ const blockerMap = group.createMap();
778
+ queue.enqueue(blockerMap.core, () => {});
779
+
780
+ const lowMap = group.createMap();
781
+ queue.enqueue(lowMap.core, () => {}, "low-priority");
782
+
783
+ expect(queue.pendingCount).toBe(1);
784
+ expect(queue.lowPriorityPendingCount).toBe(1);
785
+ expect(queue.highPriorityPendingCount).toBe(0);
786
+ });
787
+
788
+ test("should upgrade low-priority to high-priority when requested with normal priority", () => {
789
+ setMaxInFlightLoadsPerPeer(1);
790
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
791
+ const node = createTestNode();
792
+ const group = node.createGroup();
793
+
794
+ // Block the queue
795
+ const blockerMap = group.createMap();
796
+ queue.enqueue(blockerMap.core, () => {});
797
+
798
+ const targetMap = group.createMap();
799
+ let callbackCount = 0;
800
+
801
+ // First enqueue as low-priority
802
+ queue.enqueue(
803
+ targetMap.core,
804
+ () => {
805
+ callbackCount++;
806
+ },
807
+ "low-priority",
808
+ );
809
+
810
+ expect(queue.lowPriorityPendingCount).toBe(1);
811
+ expect(queue.highPriorityPendingCount).toBe(0);
812
+
813
+ // Upgrade by requesting with normal priority
814
+ queue.enqueue(targetMap.core, () => {
815
+ callbackCount += 10;
816
+ });
817
+
818
+ // Should have moved to high-priority queue
819
+ expect(queue.lowPriorityPendingCount).toBe(0);
820
+ expect(queue.highPriorityPendingCount).toBe(1);
821
+
822
+ // Complete blocker and verify upgraded callback is used
823
+ queue.trackComplete(blockerMap.core);
824
+ expect(callbackCount).toBe(10); // Upgraded callback should be called
825
+ });
826
+
827
+ test("should not downgrade high-priority to low-priority", () => {
828
+ setMaxInFlightLoadsPerPeer(1);
829
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
830
+ const node = createTestNode();
831
+ const group = node.createGroup();
832
+
833
+ // Block the queue
834
+ const blockerMap = group.createMap();
835
+ queue.enqueue(blockerMap.core, () => {});
836
+
837
+ const targetMap = group.createMap();
838
+
839
+ // First enqueue as high-priority
840
+ queue.enqueue(targetMap.core, () => {});
841
+
842
+ expect(queue.highPriorityPendingCount).toBe(1);
843
+ expect(queue.lowPriorityPendingCount).toBe(0);
844
+
845
+ // Try to enqueue as low-priority
846
+ queue.enqueue(targetMap.core, () => {}, "low-priority");
847
+
848
+ // Should still be in high-priority queue (no change)
849
+ expect(queue.highPriorityPendingCount).toBe(1);
850
+ expect(queue.lowPriorityPendingCount).toBe(0);
851
+ });
852
+
853
+ test("should process mixed priority requests in correct order", () => {
854
+ setMaxInFlightLoadsPerPeer(1);
855
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
856
+ const node = createTestNode();
857
+ const group = node.createGroup();
858
+
859
+ // Block the queue first
860
+ const blockerMap = group.createMap();
861
+ queue.enqueue(blockerMap.core, () => {});
862
+
863
+ const order: string[] = [];
864
+
865
+ // Interleave low and high priority
866
+ const low1 = group.createMap();
867
+ const high1 = group.createMap();
868
+ const low2 = group.createMap();
869
+ const high2 = group.createMap();
870
+ const low3 = group.createMap();
871
+
872
+ queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
873
+ queue.enqueue(high1.core, () => order.push("high1"));
874
+ queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
875
+ queue.enqueue(high2.core, () => order.push("high2"));
876
+ queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
877
+
878
+ expect(queue.highPriorityPendingCount).toBe(2);
879
+ expect(queue.lowPriorityPendingCount).toBe(3);
880
+
881
+ // Process all
882
+ queue.trackComplete(blockerMap.core);
883
+ queue.trackComplete(high1.core);
884
+ queue.trackComplete(high2.core);
885
+ queue.trackComplete(low1.core);
886
+ queue.trackComplete(low2.core);
887
+
888
+ // High priority should come first, then low priority
889
+ expect(order).toEqual(["high1", "high2", "low1", "low2", "low3"]);
890
+ });
891
+
892
+ test("should handle upgrade when item is the only one in low-priority queue", () => {
893
+ setMaxInFlightLoadsPerPeer(1);
894
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
895
+ const node = createTestNode();
896
+ const group = node.createGroup();
897
+
898
+ // Block the queue
899
+ const blockerMap = group.createMap();
900
+ queue.enqueue(blockerMap.core, () => {});
901
+
902
+ const targetMap = group.createMap();
903
+
904
+ // Add single low-priority item
905
+ queue.enqueue(targetMap.core, () => {}, "low-priority");
906
+
907
+ expect(queue.lowPriorityPendingCount).toBe(1);
908
+
909
+ // Upgrade it
910
+ queue.enqueue(targetMap.core, () => {});
911
+
912
+ expect(queue.lowPriorityPendingCount).toBe(0);
913
+ expect(queue.highPriorityPendingCount).toBe(1);
914
+ });
915
+
916
+ test("should handle upgrade when item is in the middle of low-priority queue", () => {
917
+ setMaxInFlightLoadsPerPeer(1);
918
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
919
+ const node = createTestNode();
920
+ const group = node.createGroup();
921
+
922
+ // Block the queue
923
+ const blockerMap = group.createMap();
924
+ queue.enqueue(blockerMap.core, () => {});
925
+
926
+ const order: string[] = [];
927
+ const low1 = group.createMap();
928
+ const low2 = group.createMap();
929
+ const low3 = group.createMap();
930
+
931
+ // Add three low-priority items
932
+ queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
933
+ queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
934
+ queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
935
+
936
+ expect(queue.lowPriorityPendingCount).toBe(3);
937
+
938
+ // Upgrade the middle one (uses new callback)
939
+ queue.enqueue(low2.core, () => order.push("upgraded"));
940
+
941
+ expect(queue.lowPriorityPendingCount).toBe(2);
942
+ expect(queue.highPriorityPendingCount).toBe(1);
943
+
944
+ // Process all
945
+ queue.trackComplete(blockerMap.core);
946
+ queue.trackComplete(low2.core);
947
+ queue.trackComplete(low1.core);
948
+
949
+ // upgraded should be first (high priority with new callback), then low1 and low3
950
+ expect(order).toEqual(["upgraded", "low1", "low3"]);
951
+ });
952
+
953
+ test("should execute immediately when upgrading low-priority with immediate mode", () => {
954
+ setMaxInFlightLoadsPerPeer(1);
955
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
956
+ const node = createTestNode();
957
+ const group = node.createGroup();
958
+
959
+ // Block the queue
960
+ const blockerMap = group.createMap();
961
+ queue.enqueue(blockerMap.core, () => {});
962
+
963
+ const targetMap = group.createMap();
964
+ let originalCallbackCalled = false;
965
+ let immediateCallbackCalled = false;
966
+
967
+ // Add low-priority item
968
+ queue.enqueue(
969
+ targetMap.core,
970
+ () => {
971
+ originalCallbackCalled = true;
972
+ },
973
+ "low-priority",
974
+ );
975
+
976
+ expect(queue.lowPriorityPendingCount).toBe(1);
977
+ expect(originalCallbackCalled).toBe(false);
978
+
979
+ // Upgrade with immediate mode - should execute immediately with new callback
980
+ queue.enqueue(
981
+ targetMap.core,
982
+ () => {
983
+ immediateCallbackCalled = true;
984
+ },
985
+ "immediate",
986
+ );
987
+
988
+ // Should have been executed immediately, not moved to high-priority queue
989
+ expect(queue.lowPriorityPendingCount).toBe(0);
990
+ expect(queue.highPriorityPendingCount).toBe(0);
991
+ expect(originalCallbackCalled).toBe(false); // Original callback not called
992
+ expect(immediateCallbackCalled).toBe(true); // New callback called
993
+ // Should be in-flight now (2 because blocker is also in-flight)
994
+ expect(queue.inFlightCount).toBe(2);
995
+ });
996
+
997
+ test("should execute immediately when upgrading high-priority with immediate mode", () => {
998
+ setMaxInFlightLoadsPerPeer(1);
999
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
1000
+ const node = createTestNode();
1001
+ const group = node.createGroup();
1002
+
1003
+ // Block the queue
1004
+ const blockerMap = group.createMap();
1005
+ queue.enqueue(blockerMap.core, () => {});
1006
+
1007
+ const targetMap = group.createMap();
1008
+ let originalCallbackCalled = false;
1009
+ let immediateCallbackCalled = false;
1010
+
1011
+ // Add high-priority item (it will be queued since blocker is in-flight)
1012
+ queue.enqueue(targetMap.core, () => {
1013
+ originalCallbackCalled = true;
1014
+ });
1015
+
1016
+ expect(queue.highPriorityPendingCount).toBe(1);
1017
+ expect(originalCallbackCalled).toBe(false);
1018
+
1019
+ // Request with immediate mode - should execute immediately with new callback
1020
+ queue.enqueue(
1021
+ targetMap.core,
1022
+ () => {
1023
+ immediateCallbackCalled = true;
1024
+ },
1025
+ "immediate",
1026
+ );
1027
+
1028
+ // Should have been executed immediately, removed from high-priority queue
1029
+ expect(queue.highPriorityPendingCount).toBe(0);
1030
+ expect(originalCallbackCalled).toBe(false); // Original callback not called
1031
+ expect(immediateCallbackCalled).toBe(true); // New callback called
1032
+ // Should be in-flight now (2 because blocker is also in-flight)
1033
+ expect(queue.inFlightCount).toBe(2);
1034
+ });
1035
+
1036
+ test("should skip immediate request on an in-flight value", () => {
1037
+ setMaxInFlightLoadsPerPeer(10);
1038
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
1039
+ const node = createTestNode();
1040
+ const group = node.createGroup();
1041
+
1042
+ const targetMap = group.createMap();
1043
+ let firstCallbackCount = 0;
1044
+ let secondCallbackCount = 0;
1045
+
1046
+ // First enqueue - should go in-flight immediately
1047
+ queue.enqueue(targetMap.core, () => {
1048
+ firstCallbackCount++;
1049
+ });
1050
+
1051
+ expect(queue.inFlightCount).toBe(1);
1052
+ expect(firstCallbackCount).toBe(1);
1053
+
1054
+ // Immediate request on in-flight value - should be skipped
1055
+ queue.enqueue(
1056
+ targetMap.core,
1057
+ () => {
1058
+ secondCallbackCount++;
1059
+ },
1060
+ "immediate",
1061
+ );
1062
+
1063
+ // Should not have sent again
1064
+ expect(queue.inFlightCount).toBe(1);
1065
+ expect(firstCallbackCount).toBe(1);
1066
+ expect(secondCallbackCount).toBe(0);
1067
+ });
1068
+
1069
+ test("should skip low-priority request on an in-flight value", () => {
1070
+ setMaxInFlightLoadsPerPeer(10);
1071
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
1072
+ const node = createTestNode();
1073
+ const group = node.createGroup();
1074
+
1075
+ const targetMap = group.createMap();
1076
+ let firstCallbackCount = 0;
1077
+ let secondCallbackCount = 0;
1078
+
1079
+ // First enqueue - should go in-flight immediately
1080
+ queue.enqueue(targetMap.core, () => {
1081
+ firstCallbackCount++;
1082
+ });
1083
+
1084
+ expect(queue.inFlightCount).toBe(1);
1085
+ expect(firstCallbackCount).toBe(1);
1086
+
1087
+ // Low-priority request on in-flight value - should be skipped
1088
+ queue.enqueue(
1089
+ targetMap.core,
1090
+ () => {
1091
+ secondCallbackCount++;
1092
+ },
1093
+ "low-priority",
1094
+ );
1095
+
1096
+ // Should not have sent again or queued
1097
+ expect(queue.inFlightCount).toBe(1);
1098
+ expect(queue.lowPriorityPendingCount).toBe(0);
1099
+ expect(firstCallbackCount).toBe(1);
1100
+ expect(secondCallbackCount).toBe(0);
1101
+ });
1102
+
1103
+ test("should clear both priority queues on clear()", () => {
1104
+ setMaxInFlightLoadsPerPeer(1);
1105
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
1106
+ const node = createTestNode();
1107
+ const group = node.createGroup();
1108
+
1109
+ // Block the queue
1110
+ const blockerMap = group.createMap();
1111
+ queue.enqueue(blockerMap.core, () => {});
1112
+
1113
+ const highMap = group.createMap();
1114
+ const lowMap = group.createMap();
1115
+
1116
+ queue.enqueue(highMap.core, () => {});
1117
+ queue.enqueue(lowMap.core, () => {}, "low-priority");
1118
+
1119
+ expect(queue.highPriorityPendingCount).toBe(1);
1120
+ expect(queue.lowPriorityPendingCount).toBe(1);
1121
+
1122
+ queue.clear();
1123
+
1124
+ expect(queue.highPriorityPendingCount).toBe(0);
1125
+ expect(queue.lowPriorityPendingCount).toBe(0);
1126
+ expect(queue.pendingCount).toBe(0);
1127
+ });
1128
+ });
1129
+ });