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,814 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { CO_VALUE_LOADING_CONFIG, setMaxInFlightLoadsPerPeer, } from "../config.js";
3
+ import { OutgoingLoadQueue } from "../queue/OutgoingLoadQueue.js";
4
+ import { createTestNode } from "./testUtils.js";
5
+ const TEST_PEER_ID = "test-peer";
6
+ // Store original config values
7
+ let originalMaxInFlightLoads;
8
+ let originalTimeout;
9
+ beforeEach(() => {
10
+ originalMaxInFlightLoads =
11
+ CO_VALUE_LOADING_CONFIG.MAX_IN_FLIGHT_LOADS_PER_PEER;
12
+ originalTimeout = CO_VALUE_LOADING_CONFIG.TIMEOUT;
13
+ });
14
+ afterEach(() => {
15
+ // Restore original config
16
+ setMaxInFlightLoadsPerPeer(originalMaxInFlightLoads);
17
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = originalTimeout;
18
+ vi.useRealTimers();
19
+ });
20
+ describe("OutgoingLoadQueue", () => {
21
+ describe("basic enqueue behavior", () => {
22
+ test("should call sendCallback immediately when queue has capacity", () => {
23
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
24
+ const node = createTestNode();
25
+ const group = node.createGroup();
26
+ const map = group.createMap();
27
+ let callbackCalled = false;
28
+ queue.enqueue(map.core, () => {
29
+ callbackCalled = true;
30
+ });
31
+ expect(callbackCalled).toBe(true);
32
+ expect(queue.inFlightCount).toBe(1);
33
+ });
34
+ test("should track sent request in inFlightLoads", () => {
35
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
36
+ const node = createTestNode();
37
+ const group = node.createGroup();
38
+ const map = group.createMap();
39
+ queue.enqueue(map.core, () => { });
40
+ expect(queue.inFlightCount).toBe(1);
41
+ });
42
+ });
43
+ describe("FIFO ordering", () => {
44
+ test("should maintain FIFO order for pending requests", () => {
45
+ setMaxInFlightLoadsPerPeer(1);
46
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
47
+ const node = createTestNode();
48
+ const group = node.createGroup();
49
+ // Block the queue first
50
+ const blockerMap = group.createMap();
51
+ queue.enqueue(blockerMap.core, () => { });
52
+ // Enqueue multiple CoValues
53
+ const map1 = group.createMap();
54
+ const map2 = group.createMap();
55
+ const map3 = group.createMap();
56
+ const order = [];
57
+ queue.enqueue(map1.core, () => order.push("map1"));
58
+ queue.enqueue(map2.core, () => order.push("map2"));
59
+ queue.enqueue(map3.core, () => order.push("map3"));
60
+ // Complete requests one by one
61
+ queue.trackComplete(blockerMap.core);
62
+ queue.trackComplete(map1.core);
63
+ queue.trackComplete(map2.core);
64
+ expect(order).toEqual(["map1", "map2", "map3"]);
65
+ });
66
+ });
67
+ describe("throttling", () => {
68
+ test("should queue requests when at capacity limit", () => {
69
+ setMaxInFlightLoadsPerPeer(2);
70
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
71
+ const node = createTestNode();
72
+ const group = node.createGroup();
73
+ const map1 = group.createMap();
74
+ const map2 = group.createMap();
75
+ const map3 = group.createMap();
76
+ let callback3Called = false;
77
+ queue.enqueue(map1.core, () => { });
78
+ queue.enqueue(map2.core, () => { });
79
+ queue.enqueue(map3.core, () => {
80
+ callback3Called = true;
81
+ });
82
+ // First two should be in flight
83
+ expect(queue.inFlightCount).toBe(2);
84
+ // Third should be pending
85
+ expect(queue.pendingCount).toBe(1);
86
+ expect(callback3Called).toBe(false);
87
+ });
88
+ test("should process queued requests when a slot becomes available", () => {
89
+ setMaxInFlightLoadsPerPeer(2);
90
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
91
+ const node = createTestNode();
92
+ const group = node.createGroup();
93
+ const map1 = group.createMap();
94
+ const map2 = group.createMap();
95
+ const map3 = group.createMap();
96
+ let callback3Called = false;
97
+ queue.enqueue(map1.core, () => { });
98
+ queue.enqueue(map2.core, () => { });
99
+ queue.enqueue(map3.core, () => {
100
+ callback3Called = true;
101
+ });
102
+ expect(callback3Called).toBe(false);
103
+ // Complete one request
104
+ queue.trackComplete(map1.core);
105
+ // Third should now be processed
106
+ expect(callback3Called).toBe(true);
107
+ expect(queue.inFlightCount).toBe(2);
108
+ expect(queue.pendingCount).toBe(0);
109
+ });
110
+ });
111
+ describe("request deduplication", () => {
112
+ test("should skip duplicate enqueue while pending", () => {
113
+ setMaxInFlightLoadsPerPeer(1);
114
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
115
+ const node = createTestNode();
116
+ const group = node.createGroup();
117
+ const blockerMap = group.createMap();
118
+ const targetMap = group.createMap();
119
+ let targetCallbackCount = 0;
120
+ let duplicateCallbackCount = 0;
121
+ queue.enqueue(blockerMap.core, () => { });
122
+ queue.enqueue(targetMap.core, () => {
123
+ targetCallbackCount += 1;
124
+ });
125
+ queue.enqueue(targetMap.core, () => {
126
+ duplicateCallbackCount += 1;
127
+ });
128
+ expect(queue.pendingCount).toBe(1);
129
+ expect(targetCallbackCount).toBe(0);
130
+ expect(duplicateCallbackCount).toBe(0);
131
+ queue.trackComplete(blockerMap.core);
132
+ expect(targetCallbackCount).toBe(1);
133
+ expect(duplicateCallbackCount).toBe(0);
134
+ });
135
+ test("should skip duplicate enqueue while in-flight", () => {
136
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
137
+ const node = createTestNode();
138
+ const group = node.createGroup();
139
+ const map = group.createMap();
140
+ let duplicateCallbackCount = 0;
141
+ queue.enqueue(map.core, () => { });
142
+ queue.enqueue(map.core, () => {
143
+ duplicateCallbackCount += 1;
144
+ });
145
+ expect(queue.inFlightCount).toBe(1);
146
+ expect(duplicateCallbackCount).toBe(0);
147
+ });
148
+ test("should skip duplicate enqueue for same CoValue ID", () => {
149
+ setMaxInFlightLoadsPerPeer(1);
150
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
151
+ const node = createTestNode();
152
+ const otherNode = createTestNode();
153
+ const group = node.createGroup();
154
+ const availableMap = group.createMap();
155
+ const sameIdCoValue = otherNode.getCoValue(availableMap.id);
156
+ queue.enqueue(availableMap.core, () => { });
157
+ queue.trackComplete(availableMap.core);
158
+ // Block capacity so the next enqueue stays pending
159
+ const blockerMap = group.createMap();
160
+ queue.enqueue(blockerMap.core, () => { });
161
+ let duplicateCallbackCount = 0;
162
+ queue.enqueue(availableMap.core, () => { });
163
+ queue.enqueue(sameIdCoValue, () => {
164
+ duplicateCallbackCount += 1;
165
+ });
166
+ expect(queue.pendingCount).toBe(1);
167
+ expect(duplicateCallbackCount).toBe(0);
168
+ });
169
+ });
170
+ describe("timeout behavior", () => {
171
+ test("should mark CoValue as not found in peer after timeout", async () => {
172
+ vi.useFakeTimers();
173
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
174
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
175
+ const node = createTestNode();
176
+ // Get an unavailable CoValue
177
+ const coValue = node.getCoValue("co_zTestTimeoutCoValue0001");
178
+ queue.enqueue(coValue, () => { });
179
+ expect(queue.inFlightCount).toBe(1);
180
+ // Advance time past the timeout
181
+ await vi.advanceTimersByTimeAsync(1001);
182
+ // Should be removed from in-flight
183
+ expect(queue.inFlightCount).toBe(0);
184
+ // Should be marked as not found
185
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
186
+ });
187
+ test("should free the queue slot and process pending requests on timeout", async () => {
188
+ vi.useFakeTimers();
189
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
190
+ setMaxInFlightLoadsPerPeer(1);
191
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
192
+ const node = createTestNode();
193
+ const group = node.createGroup();
194
+ const coValue1 = node.getCoValue("co_zTestTimeoutFree00000001");
195
+ const map2 = group.createMap();
196
+ let callback2Called = false;
197
+ queue.enqueue(coValue1, () => { });
198
+ queue.enqueue(map2.core, () => {
199
+ callback2Called = true;
200
+ });
201
+ expect(queue.inFlightCount).toBe(1);
202
+ expect(callback2Called).toBe(false);
203
+ // Advance time past the timeout
204
+ await vi.advanceTimersByTimeAsync(1001);
205
+ // First should have timed out, second should be processed
206
+ expect(callback2Called).toBe(true);
207
+ expect(queue.inFlightCount).toBe(1);
208
+ });
209
+ test("should timeout each in-flight load independently", async () => {
210
+ vi.useFakeTimers();
211
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
212
+ setMaxInFlightLoadsPerPeer(3);
213
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
214
+ const node = createTestNode();
215
+ const coValue1 = node.getCoValue("co_zTestSingleTimer000001");
216
+ const coValue2 = node.getCoValue("co_zTestSingleTimer000002");
217
+ const coValue3 = node.getCoValue("co_zTestSingleTimer000003");
218
+ queue.enqueue(coValue1, () => { });
219
+ await vi.advanceTimersByTimeAsync(100);
220
+ queue.enqueue(coValue2, () => { });
221
+ await vi.advanceTimersByTimeAsync(100);
222
+ queue.enqueue(coValue3, () => { });
223
+ expect(queue.inFlightCount).toBe(3);
224
+ // Advance time past the timeout
225
+ await vi.advanceTimersByTimeAsync(801);
226
+ // All three should have timed out
227
+ expect(queue.inFlightCount).toBe(2);
228
+ await vi.advanceTimersByTimeAsync(101);
229
+ expect(coValue1.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
230
+ expect(queue.inFlightCount).toBe(1);
231
+ await vi.advanceTimersByTimeAsync(101);
232
+ expect(coValue2.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
233
+ expect(queue.inFlightCount).toBe(0);
234
+ await vi.advanceTimersByTimeAsync(101);
235
+ expect(coValue3.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
236
+ expect(queue.inFlightCount).toBe(0);
237
+ });
238
+ test("should allow re-enqueue after timeout", async () => {
239
+ vi.useFakeTimers();
240
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
241
+ setMaxInFlightLoadsPerPeer(1);
242
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
243
+ const node = createTestNode();
244
+ const coValue = node.getCoValue("co_zTestTimeoutReenqueue0001");
245
+ let secondCallbackCount = 0;
246
+ queue.enqueue(coValue, () => { });
247
+ await vi.advanceTimersByTimeAsync(1001);
248
+ expect(queue.inFlightCount).toBe(0);
249
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
250
+ queue.enqueue(coValue, () => {
251
+ secondCallbackCount += 1;
252
+ });
253
+ expect(queue.inFlightCount).toBe(1);
254
+ expect(secondCallbackCount).toBe(1);
255
+ });
256
+ test("should warn but not mark as unavailable when streaming CoValue times out", async () => {
257
+ vi.useFakeTimers();
258
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
259
+ setMaxInFlightLoadsPerPeer(1);
260
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
261
+ const node = createTestNode();
262
+ const group = node.createGroup();
263
+ const map = group.createMap();
264
+ map.set("key", "value");
265
+ // Mock isStreaming to return true (CoValue is available but still streaming)
266
+ vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
267
+ queue.enqueue(map.core, () => { });
268
+ expect(queue.inFlightCount).toBe(1);
269
+ // Advance time past the timeout
270
+ await vi.advanceTimersByTimeAsync(1001);
271
+ // Should be removed from in-flight (timeout clears the slot)
272
+ expect(queue.inFlightCount).toBe(0);
273
+ // But should NOT be marked as unavailable since it's available (just streaming)
274
+ expect(map.core.isAvailable()).toBe(true);
275
+ });
276
+ });
277
+ describe("trackComplete", () => {
278
+ test("should remove from inFlightLoads and allow next request to process", () => {
279
+ setMaxInFlightLoadsPerPeer(1);
280
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
281
+ const node = createTestNode();
282
+ const group = node.createGroup();
283
+ const map1 = group.createMap();
284
+ const map2 = group.createMap();
285
+ let callback2Called = false;
286
+ queue.enqueue(map1.core, () => { });
287
+ queue.enqueue(map2.core, () => {
288
+ callback2Called = true;
289
+ });
290
+ expect(queue.inFlightCount).toBe(1);
291
+ expect(callback2Called).toBe(false);
292
+ queue.trackComplete(map1.core);
293
+ expect(callback2Called).toBe(true);
294
+ expect(queue.inFlightCount).toBe(1);
295
+ });
296
+ test("should be a no-op for unknown CoValues", () => {
297
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
298
+ const node = createTestNode();
299
+ const group = node.createGroup();
300
+ const map1 = group.createMap();
301
+ const unknownCoValue = node.getCoValue("co_zTestUnknownCoValue001");
302
+ queue.enqueue(map1.core, () => { });
303
+ expect(queue.inFlightCount).toBe(1);
304
+ // trackComplete on unknown CoValue should be a no-op
305
+ queue.trackComplete(unknownCoValue);
306
+ expect(queue.inFlightCount).toBe(1);
307
+ });
308
+ test("should allow re-enqueue after completion", () => {
309
+ setMaxInFlightLoadsPerPeer(1);
310
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
311
+ const node = createTestNode();
312
+ const group = node.createGroup();
313
+ const map = group.createMap();
314
+ let firstCallbackCount = 0;
315
+ let secondCallbackCount = 0;
316
+ queue.enqueue(map.core, () => {
317
+ firstCallbackCount += 1;
318
+ });
319
+ expect(queue.inFlightCount).toBe(1);
320
+ expect(firstCallbackCount).toBe(1);
321
+ queue.trackComplete(map.core);
322
+ queue.enqueue(map.core, () => {
323
+ secondCallbackCount += 1;
324
+ });
325
+ expect(queue.inFlightCount).toBe(1);
326
+ expect(secondCallbackCount).toBe(1);
327
+ });
328
+ test("should not complete if CoValue is streaming", () => {
329
+ setMaxInFlightLoadsPerPeer(1);
330
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
331
+ const node = createTestNode();
332
+ const group = node.createGroup();
333
+ const map1 = group.createMap();
334
+ const map2 = group.createMap();
335
+ // Mock isStreaming to return true
336
+ vi.spyOn(map1.core, "isStreaming").mockReturnValue(true);
337
+ let callback2Called = false;
338
+ queue.enqueue(map1.core, () => { });
339
+ queue.enqueue(map2.core, () => {
340
+ callback2Called = true;
341
+ });
342
+ expect(queue.inFlightCount).toBe(1);
343
+ expect(callback2Called).toBe(false);
344
+ // trackComplete should not complete because map1 is streaming
345
+ queue.trackComplete(map1.core);
346
+ // Should still be in-flight and pending should not be processed
347
+ expect(queue.inFlightCount).toBe(1);
348
+ expect(callback2Called).toBe(false);
349
+ expect(queue.pendingCount).toBe(1);
350
+ // Stop streaming
351
+ vi.spyOn(map1.core, "isStreaming").mockReturnValue(false);
352
+ // Now trackComplete should work
353
+ queue.trackComplete(map1.core);
354
+ expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
355
+ expect(callback2Called).toBe(true);
356
+ expect(queue.pendingCount).toBe(0);
357
+ });
358
+ });
359
+ describe("trackUpdate", () => {
360
+ test("should refresh timeout for in-flight streaming CoValue", async () => {
361
+ vi.useFakeTimers();
362
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
363
+ setMaxInFlightLoadsPerPeer(1);
364
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
365
+ const node = createTestNode();
366
+ const group = node.createGroup();
367
+ const map = group.createMap();
368
+ map.set("key", "value");
369
+ // Mock isStreaming to return true
370
+ vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
371
+ queue.enqueue(map.core, () => { });
372
+ expect(queue.inFlightCount).toBe(1);
373
+ // Advance time to just before timeout
374
+ await vi.advanceTimersByTimeAsync(900);
375
+ // Refresh the timeout (simulating receiving a chunk)
376
+ queue.trackUpdate(map.core);
377
+ // Advance another 200ms - would have timed out without the refresh
378
+ await vi.advanceTimersByTimeAsync(200);
379
+ // Should still be in-flight because timeout was refreshed
380
+ expect(queue.inFlightCount).toBe(1);
381
+ // Now advance past the new timeout
382
+ await vi.advanceTimersByTimeAsync(900);
383
+ // Should have timed out now
384
+ expect(queue.inFlightCount).toBe(0);
385
+ });
386
+ test("should be a no-op for unknown CoValues", () => {
387
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
388
+ const node = createTestNode();
389
+ const group = node.createGroup();
390
+ const map = group.createMap();
391
+ const unknownCoValue = node.getCoValue("co_zTestTrackUpdateUnknown01");
392
+ queue.enqueue(map.core, () => { });
393
+ expect(queue.inFlightCount).toBe(1);
394
+ // trackUpdate on unknown CoValue should be a no-op
395
+ queue.trackUpdate(unknownCoValue);
396
+ // Should still have the same in-flight count
397
+ expect(queue.inFlightCount).toBe(1);
398
+ });
399
+ });
400
+ describe("immediate mode", () => {
401
+ test("should send immediately when mode is immediate even at capacity", () => {
402
+ setMaxInFlightLoadsPerPeer(1);
403
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
404
+ const node = createTestNode();
405
+ const group = node.createGroup();
406
+ const map1 = group.createMap();
407
+ const map2 = group.createMap();
408
+ let callback1Called = false;
409
+ let callback2Called = false;
410
+ // Fill the queue to capacity
411
+ queue.enqueue(map1.core, () => {
412
+ callback1Called = true;
413
+ });
414
+ expect(callback1Called).toBe(true);
415
+ expect(queue.inFlightCount).toBe(1);
416
+ // This should bypass the capacity limit with immediate mode
417
+ queue.enqueue(map2.core, () => {
418
+ callback2Called = true;
419
+ }, "immediate");
420
+ // Both should have been called even though capacity is 1
421
+ expect(callback2Called).toBe(true);
422
+ expect(queue.inFlightCount).toBe(2);
423
+ });
424
+ test("should still track immediate requests for timeout handling", async () => {
425
+ vi.useFakeTimers();
426
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
427
+ setMaxInFlightLoadsPerPeer(1);
428
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
429
+ const node = createTestNode();
430
+ const coValue = node.getCoValue("co_zTestOverflowTimeout01");
431
+ queue.enqueue(coValue, () => { }, "immediate");
432
+ expect(queue.inFlightCount).toBe(1);
433
+ // Advance time past the timeout
434
+ await vi.advanceTimersByTimeAsync(1001);
435
+ // Should be removed from in-flight and marked as not found
436
+ expect(queue.inFlightCount).toBe(0);
437
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
438
+ });
439
+ test("should still deduplicate immediate requests", () => {
440
+ setMaxInFlightLoadsPerPeer(1);
441
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
442
+ const node = createTestNode();
443
+ const group = node.createGroup();
444
+ const map = group.createMap();
445
+ let callbackCount = 0;
446
+ queue.enqueue(map.core, () => {
447
+ callbackCount++;
448
+ }, "immediate");
449
+ queue.enqueue(map.core, () => {
450
+ callbackCount++;
451
+ }, "immediate");
452
+ // Should only be called once due to deduplication
453
+ expect(callbackCount).toBe(1);
454
+ expect(queue.inFlightCount).toBe(1);
455
+ });
456
+ test("should process pending requests when immediate request completes", () => {
457
+ setMaxInFlightLoadsPerPeer(1);
458
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
459
+ const node = createTestNode();
460
+ const group = node.createGroup();
461
+ const map1 = group.createMap();
462
+ const immediateMap = group.createMap();
463
+ const map2 = group.createMap();
464
+ let callback2Called = false;
465
+ // Fill the queue
466
+ queue.enqueue(map1.core, () => { });
467
+ // Add pending request
468
+ queue.enqueue(map2.core, () => {
469
+ callback2Called = true;
470
+ });
471
+ expect(callback2Called).toBe(false);
472
+ expect(queue.pendingCount).toBe(1);
473
+ // Add immediate request - this bypasses the queue but still counts as in-flight
474
+ queue.enqueue(immediateMap.core, () => { }, "immediate");
475
+ expect(queue.inFlightCount).toBe(2);
476
+ // Complete the immediate request
477
+ queue.trackComplete(immediateMap.core);
478
+ // Still at capacity because map1 is still in-flight
479
+ expect(callback2Called).toBe(false);
480
+ expect(queue.inFlightCount).toBe(1);
481
+ // Complete the regular request - now pending can be processed
482
+ queue.trackComplete(map1.core);
483
+ expect(callback2Called).toBe(true);
484
+ expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
485
+ });
486
+ });
487
+ describe("clear", () => {
488
+ test("should clear all in-flight loads and pending queues", () => {
489
+ setMaxInFlightLoadsPerPeer(2);
490
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
491
+ const node = createTestNode();
492
+ const group = node.createGroup();
493
+ const map1 = group.createMap();
494
+ const map2 = group.createMap();
495
+ const map3 = group.createMap();
496
+ queue.enqueue(map1.core, () => { });
497
+ queue.enqueue(map2.core, () => { });
498
+ queue.enqueue(map3.core, () => { });
499
+ expect(queue.inFlightCount).toBe(2);
500
+ expect(queue.pendingCount).toBe(1);
501
+ queue.clear();
502
+ expect(queue.inFlightCount).toBe(0);
503
+ expect(queue.pendingCount).toBe(0);
504
+ });
505
+ test("should cancel any pending timeout", async () => {
506
+ vi.useFakeTimers();
507
+ CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
508
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
509
+ const node = createTestNode();
510
+ const coValue = node.getCoValue("co_zTestClearTimeout00001");
511
+ queue.enqueue(coValue, () => { });
512
+ expect(queue.inFlightCount).toBe(1);
513
+ // Clear before timeout
514
+ queue.clear();
515
+ expect(queue.inFlightCount).toBe(0);
516
+ // Advance time past the timeout
517
+ await vi.advanceTimersByTimeAsync(1001);
518
+ // Should not have marked as not found since we cleared
519
+ expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).not.toBe("unavailable");
520
+ });
521
+ });
522
+ describe("priority queue", () => {
523
+ test("should process high-priority requests before low-priority", () => {
524
+ setMaxInFlightLoadsPerPeer(1);
525
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
526
+ const node = createTestNode();
527
+ const group = node.createGroup();
528
+ // Block the queue first
529
+ const blockerMap = group.createMap();
530
+ queue.enqueue(blockerMap.core, () => { });
531
+ // Enqueue low-priority requests
532
+ const lowMap1 = group.createMap();
533
+ const lowMap2 = group.createMap();
534
+ // Enqueue high-priority request
535
+ const highMap = group.createMap();
536
+ const order = [];
537
+ // Add low-priority first
538
+ queue.enqueue(lowMap1.core, () => order.push("low1"), "low-priority");
539
+ queue.enqueue(lowMap2.core, () => order.push("low2"), "low-priority");
540
+ // Add high-priority after
541
+ queue.enqueue(highMap.core, () => order.push("high"));
542
+ expect(queue.pendingCount).toBe(3);
543
+ expect(queue.highPriorityPendingCount).toBe(1);
544
+ expect(queue.lowPriorityPendingCount).toBe(2);
545
+ // Complete the blocker and process requests one by one
546
+ queue.trackComplete(blockerMap.core);
547
+ expect(order).toEqual(["high"]);
548
+ queue.trackComplete(highMap.core);
549
+ expect(order).toEqual(["high", "low1"]);
550
+ queue.trackComplete(lowMap1.core);
551
+ expect(order).toEqual(["high", "low1", "low2"]);
552
+ });
553
+ test("should add low-priority request to low-priority queue", () => {
554
+ setMaxInFlightLoadsPerPeer(1);
555
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
556
+ const node = createTestNode();
557
+ const group = node.createGroup();
558
+ // Block the queue
559
+ const blockerMap = group.createMap();
560
+ queue.enqueue(blockerMap.core, () => { });
561
+ const lowMap = group.createMap();
562
+ queue.enqueue(lowMap.core, () => { }, "low-priority");
563
+ expect(queue.pendingCount).toBe(1);
564
+ expect(queue.lowPriorityPendingCount).toBe(1);
565
+ expect(queue.highPriorityPendingCount).toBe(0);
566
+ });
567
+ test("should upgrade low-priority to high-priority when requested with normal priority", () => {
568
+ setMaxInFlightLoadsPerPeer(1);
569
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
570
+ const node = createTestNode();
571
+ const group = node.createGroup();
572
+ // Block the queue
573
+ const blockerMap = group.createMap();
574
+ queue.enqueue(blockerMap.core, () => { });
575
+ const targetMap = group.createMap();
576
+ let callbackCount = 0;
577
+ // First enqueue as low-priority
578
+ queue.enqueue(targetMap.core, () => {
579
+ callbackCount++;
580
+ }, "low-priority");
581
+ expect(queue.lowPriorityPendingCount).toBe(1);
582
+ expect(queue.highPriorityPendingCount).toBe(0);
583
+ // Upgrade by requesting with normal priority
584
+ queue.enqueue(targetMap.core, () => {
585
+ callbackCount += 10;
586
+ });
587
+ // Should have moved to high-priority queue
588
+ expect(queue.lowPriorityPendingCount).toBe(0);
589
+ expect(queue.highPriorityPendingCount).toBe(1);
590
+ // Complete blocker and verify upgraded callback is used
591
+ queue.trackComplete(blockerMap.core);
592
+ expect(callbackCount).toBe(10); // Upgraded callback should be called
593
+ });
594
+ test("should not downgrade high-priority to low-priority", () => {
595
+ setMaxInFlightLoadsPerPeer(1);
596
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
597
+ const node = createTestNode();
598
+ const group = node.createGroup();
599
+ // Block the queue
600
+ const blockerMap = group.createMap();
601
+ queue.enqueue(blockerMap.core, () => { });
602
+ const targetMap = group.createMap();
603
+ // First enqueue as high-priority
604
+ queue.enqueue(targetMap.core, () => { });
605
+ expect(queue.highPriorityPendingCount).toBe(1);
606
+ expect(queue.lowPriorityPendingCount).toBe(0);
607
+ // Try to enqueue as low-priority
608
+ queue.enqueue(targetMap.core, () => { }, "low-priority");
609
+ // Should still be in high-priority queue (no change)
610
+ expect(queue.highPriorityPendingCount).toBe(1);
611
+ expect(queue.lowPriorityPendingCount).toBe(0);
612
+ });
613
+ test("should process mixed priority requests in correct order", () => {
614
+ setMaxInFlightLoadsPerPeer(1);
615
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
616
+ const node = createTestNode();
617
+ const group = node.createGroup();
618
+ // Block the queue first
619
+ const blockerMap = group.createMap();
620
+ queue.enqueue(blockerMap.core, () => { });
621
+ const order = [];
622
+ // Interleave low and high priority
623
+ const low1 = group.createMap();
624
+ const high1 = group.createMap();
625
+ const low2 = group.createMap();
626
+ const high2 = group.createMap();
627
+ const low3 = group.createMap();
628
+ queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
629
+ queue.enqueue(high1.core, () => order.push("high1"));
630
+ queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
631
+ queue.enqueue(high2.core, () => order.push("high2"));
632
+ queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
633
+ expect(queue.highPriorityPendingCount).toBe(2);
634
+ expect(queue.lowPriorityPendingCount).toBe(3);
635
+ // Process all
636
+ queue.trackComplete(blockerMap.core);
637
+ queue.trackComplete(high1.core);
638
+ queue.trackComplete(high2.core);
639
+ queue.trackComplete(low1.core);
640
+ queue.trackComplete(low2.core);
641
+ // High priority should come first, then low priority
642
+ expect(order).toEqual(["high1", "high2", "low1", "low2", "low3"]);
643
+ });
644
+ test("should handle upgrade when item is the only one in low-priority queue", () => {
645
+ setMaxInFlightLoadsPerPeer(1);
646
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
647
+ const node = createTestNode();
648
+ const group = node.createGroup();
649
+ // Block the queue
650
+ const blockerMap = group.createMap();
651
+ queue.enqueue(blockerMap.core, () => { });
652
+ const targetMap = group.createMap();
653
+ // Add single low-priority item
654
+ queue.enqueue(targetMap.core, () => { }, "low-priority");
655
+ expect(queue.lowPriorityPendingCount).toBe(1);
656
+ // Upgrade it
657
+ queue.enqueue(targetMap.core, () => { });
658
+ expect(queue.lowPriorityPendingCount).toBe(0);
659
+ expect(queue.highPriorityPendingCount).toBe(1);
660
+ });
661
+ test("should handle upgrade when item is in the middle of low-priority queue", () => {
662
+ setMaxInFlightLoadsPerPeer(1);
663
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
664
+ const node = createTestNode();
665
+ const group = node.createGroup();
666
+ // Block the queue
667
+ const blockerMap = group.createMap();
668
+ queue.enqueue(blockerMap.core, () => { });
669
+ const order = [];
670
+ const low1 = group.createMap();
671
+ const low2 = group.createMap();
672
+ const low3 = group.createMap();
673
+ // Add three low-priority items
674
+ queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
675
+ queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
676
+ queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
677
+ expect(queue.lowPriorityPendingCount).toBe(3);
678
+ // Upgrade the middle one (uses new callback)
679
+ queue.enqueue(low2.core, () => order.push("upgraded"));
680
+ expect(queue.lowPriorityPendingCount).toBe(2);
681
+ expect(queue.highPriorityPendingCount).toBe(1);
682
+ // Process all
683
+ queue.trackComplete(blockerMap.core);
684
+ queue.trackComplete(low2.core);
685
+ queue.trackComplete(low1.core);
686
+ // upgraded should be first (high priority with new callback), then low1 and low3
687
+ expect(order).toEqual(["upgraded", "low1", "low3"]);
688
+ });
689
+ test("should execute immediately when upgrading low-priority with immediate mode", () => {
690
+ setMaxInFlightLoadsPerPeer(1);
691
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
692
+ const node = createTestNode();
693
+ const group = node.createGroup();
694
+ // Block the queue
695
+ const blockerMap = group.createMap();
696
+ queue.enqueue(blockerMap.core, () => { });
697
+ const targetMap = group.createMap();
698
+ let originalCallbackCalled = false;
699
+ let immediateCallbackCalled = false;
700
+ // Add low-priority item
701
+ queue.enqueue(targetMap.core, () => {
702
+ originalCallbackCalled = true;
703
+ }, "low-priority");
704
+ expect(queue.lowPriorityPendingCount).toBe(1);
705
+ expect(originalCallbackCalled).toBe(false);
706
+ // Upgrade with immediate mode - should execute immediately with new callback
707
+ queue.enqueue(targetMap.core, () => {
708
+ immediateCallbackCalled = true;
709
+ }, "immediate");
710
+ // Should have been executed immediately, not moved to high-priority queue
711
+ expect(queue.lowPriorityPendingCount).toBe(0);
712
+ expect(queue.highPriorityPendingCount).toBe(0);
713
+ expect(originalCallbackCalled).toBe(false); // Original callback not called
714
+ expect(immediateCallbackCalled).toBe(true); // New callback called
715
+ // Should be in-flight now (2 because blocker is also in-flight)
716
+ expect(queue.inFlightCount).toBe(2);
717
+ });
718
+ test("should execute immediately when upgrading high-priority with immediate mode", () => {
719
+ setMaxInFlightLoadsPerPeer(1);
720
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
721
+ const node = createTestNode();
722
+ const group = node.createGroup();
723
+ // Block the queue
724
+ const blockerMap = group.createMap();
725
+ queue.enqueue(blockerMap.core, () => { });
726
+ const targetMap = group.createMap();
727
+ let originalCallbackCalled = false;
728
+ let immediateCallbackCalled = false;
729
+ // Add high-priority item (it will be queued since blocker is in-flight)
730
+ queue.enqueue(targetMap.core, () => {
731
+ originalCallbackCalled = true;
732
+ });
733
+ expect(queue.highPriorityPendingCount).toBe(1);
734
+ expect(originalCallbackCalled).toBe(false);
735
+ // Request with immediate mode - should execute immediately with new callback
736
+ queue.enqueue(targetMap.core, () => {
737
+ immediateCallbackCalled = true;
738
+ }, "immediate");
739
+ // Should have been executed immediately, removed from high-priority queue
740
+ expect(queue.highPriorityPendingCount).toBe(0);
741
+ expect(originalCallbackCalled).toBe(false); // Original callback not called
742
+ expect(immediateCallbackCalled).toBe(true); // New callback called
743
+ // Should be in-flight now (2 because blocker is also in-flight)
744
+ expect(queue.inFlightCount).toBe(2);
745
+ });
746
+ test("should skip immediate request on an in-flight value", () => {
747
+ setMaxInFlightLoadsPerPeer(10);
748
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
749
+ const node = createTestNode();
750
+ const group = node.createGroup();
751
+ const targetMap = group.createMap();
752
+ let firstCallbackCount = 0;
753
+ let secondCallbackCount = 0;
754
+ // First enqueue - should go in-flight immediately
755
+ queue.enqueue(targetMap.core, () => {
756
+ firstCallbackCount++;
757
+ });
758
+ expect(queue.inFlightCount).toBe(1);
759
+ expect(firstCallbackCount).toBe(1);
760
+ // Immediate request on in-flight value - should be skipped
761
+ queue.enqueue(targetMap.core, () => {
762
+ secondCallbackCount++;
763
+ }, "immediate");
764
+ // Should not have sent again
765
+ expect(queue.inFlightCount).toBe(1);
766
+ expect(firstCallbackCount).toBe(1);
767
+ expect(secondCallbackCount).toBe(0);
768
+ });
769
+ test("should skip low-priority request on an in-flight value", () => {
770
+ setMaxInFlightLoadsPerPeer(10);
771
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
772
+ const node = createTestNode();
773
+ const group = node.createGroup();
774
+ const targetMap = group.createMap();
775
+ let firstCallbackCount = 0;
776
+ let secondCallbackCount = 0;
777
+ // First enqueue - should go in-flight immediately
778
+ queue.enqueue(targetMap.core, () => {
779
+ firstCallbackCount++;
780
+ });
781
+ expect(queue.inFlightCount).toBe(1);
782
+ expect(firstCallbackCount).toBe(1);
783
+ // Low-priority request on in-flight value - should be skipped
784
+ queue.enqueue(targetMap.core, () => {
785
+ secondCallbackCount++;
786
+ }, "low-priority");
787
+ // Should not have sent again or queued
788
+ expect(queue.inFlightCount).toBe(1);
789
+ expect(queue.lowPriorityPendingCount).toBe(0);
790
+ expect(firstCallbackCount).toBe(1);
791
+ expect(secondCallbackCount).toBe(0);
792
+ });
793
+ test("should clear both priority queues on clear()", () => {
794
+ setMaxInFlightLoadsPerPeer(1);
795
+ const queue = new OutgoingLoadQueue(TEST_PEER_ID);
796
+ const node = createTestNode();
797
+ const group = node.createGroup();
798
+ // Block the queue
799
+ const blockerMap = group.createMap();
800
+ queue.enqueue(blockerMap.core, () => { });
801
+ const highMap = group.createMap();
802
+ const lowMap = group.createMap();
803
+ queue.enqueue(highMap.core, () => { });
804
+ queue.enqueue(lowMap.core, () => { }, "low-priority");
805
+ expect(queue.highPriorityPendingCount).toBe(1);
806
+ expect(queue.lowPriorityPendingCount).toBe(1);
807
+ queue.clear();
808
+ expect(queue.highPriorityPendingCount).toBe(0);
809
+ expect(queue.lowPriorityPendingCount).toBe(0);
810
+ expect(queue.pendingCount).toBe(0);
811
+ });
812
+ });
813
+ });
814
+ //# sourceMappingURL=OutgoingLoadQueue.test.js.map