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