cojson 0.19.19 → 0.19.21
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/dist/coValueCore/coValueCore.d.ts +9 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +21 -0
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +10 -10
- package/dist/coValues/account.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +7 -1
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +1 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +5 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/knownState.js +15 -0
- package/dist/knownState.js.map +1 -1
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +8 -2
- package/dist/localNode.js.map +1 -1
- package/dist/queue/IncomingMessagesQueue.d.ts +6 -7
- package/dist/queue/IncomingMessagesQueue.d.ts.map +1 -1
- package/dist/queue/IncomingMessagesQueue.js +7 -30
- package/dist/queue/IncomingMessagesQueue.js.map +1 -1
- package/dist/queue/LinkedList.d.ts +1 -1
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/StorageStreamingQueue.d.ts +43 -0
- package/dist/queue/StorageStreamingQueue.d.ts.map +1 -0
- package/dist/queue/StorageStreamingQueue.js +70 -0
- package/dist/queue/StorageStreamingQueue.js.map +1 -0
- package/dist/storage/knownState.d.ts +5 -0
- package/dist/storage/knownState.d.ts.map +1 -1
- package/dist/storage/knownState.js +11 -0
- package/dist/storage/knownState.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +2 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +18 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +2 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +20 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +2 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +40 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +9 -2
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +71 -44
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +20 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts +34 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +185 -46
- package/dist/sync.js.map +1 -1
- package/dist/tests/IncomingMessagesQueue.test.js +4 -150
- package/dist/tests/IncomingMessagesQueue.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +91 -0
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +91 -0
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/StorageStreamingQueue.test.d.ts +2 -0
- package/dist/tests/StorageStreamingQueue.test.d.ts.map +1 -0
- package/dist/tests/StorageStreamingQueue.test.js +213 -0
- package/dist/tests/StorageStreamingQueue.test.js.map +1 -0
- package/dist/tests/SyncManager.processQueues.test.d.ts +2 -0
- package/dist/tests/SyncManager.processQueues.test.d.ts.map +1 -0
- package/dist/tests/SyncManager.processQueues.test.js +208 -0
- package/dist/tests/SyncManager.processQueues.test.js.map +1 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js +1 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.d.ts +2 -0
- package/dist/tests/knownState.lazyLoading.test.d.ts.map +1 -0
- package/dist/tests/knownState.lazyLoading.test.js +166 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -0
- package/dist/tests/messagesTestUtils.d.ts +5 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +4 -0
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/setup.d.ts +2 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +4 -0
- package/dist/tests/setup.js.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +388 -0
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +23 -23
- package/dist/tests/sync.storage.test.js +176 -20
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +1 -1
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.js +36 -0
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +16 -4
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +2 -2
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +4 -4
- package/src/coValueCore/coValueCore.ts +26 -0
- package/src/coValues/account.ts +12 -14
- package/src/config.ts +13 -0
- package/src/exports.ts +6 -0
- package/src/ids.ts +1 -1
- package/src/knownState.ts +24 -0
- package/src/localNode.ts +9 -2
- package/src/queue/IncomingMessagesQueue.ts +7 -39
- package/src/queue/LinkedList.ts +1 -1
- package/src/queue/StorageStreamingQueue.ts +96 -0
- package/src/storage/knownState.ts +12 -0
- package/src/storage/sqlite/client.ts +31 -0
- package/src/storage/sqliteAsync/client.ts +35 -0
- package/src/storage/storageAsync.ts +51 -0
- package/src/storage/storageSync.ts +121 -55
- package/src/storage/types.ts +29 -0
- package/src/sync.ts +210 -47
- package/src/tests/IncomingMessagesQueue.test.ts +4 -206
- package/src/tests/StorageApiAsync.test.ts +136 -0
- package/src/tests/StorageApiSync.test.ts +132 -0
- package/src/tests/StorageStreamingQueue.test.ts +276 -0
- package/src/tests/SyncManager.processQueues.test.ts +287 -0
- package/src/tests/coValueCore.loadFromStorage.test.ts +3 -0
- package/src/tests/knownState.lazyLoading.test.ts +217 -0
- package/src/tests/messagesTestUtils.ts +10 -3
- package/src/tests/setup.ts +4 -0
- package/src/tests/sync.garbageCollection.test.ts +1 -3
- package/src/tests/sync.load.test.ts +483 -1
- package/src/tests/sync.mesh.test.ts +23 -23
- package/src/tests/sync.storage.test.ts +224 -32
- package/src/tests/sync.test.ts +1 -9
- package/src/tests/testStorage.ts +38 -0
- package/src/tests/testUtils.ts +16 -4
- package/vitest.config.ts +1 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
3
|
+
import { StorageStreamingQueue } from "../queue/StorageStreamingQueue.js";
|
|
4
|
+
import {
|
|
5
|
+
SyncMessagesLog,
|
|
6
|
+
loadCoValueOrFail,
|
|
7
|
+
setupTestNode,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "./testUtils.js";
|
|
10
|
+
|
|
11
|
+
describe("SyncManager.processQueues", () => {
|
|
12
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
SyncMessagesLog.clear();
|
|
16
|
+
jazzCloud = setupTestNode({
|
|
17
|
+
isSyncServer: true,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("incoming messages processing", () => {
|
|
22
|
+
test("should process incoming messages from peers", async () => {
|
|
23
|
+
const client = setupTestNode();
|
|
24
|
+
client.connectToSyncServer();
|
|
25
|
+
|
|
26
|
+
const group = jazzCloud.node.createGroup();
|
|
27
|
+
const map = group.createMap();
|
|
28
|
+
map.set("hello", "world", "trusting");
|
|
29
|
+
|
|
30
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
31
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should process multiple messages in sequence", async () => {
|
|
35
|
+
const client = setupTestNode();
|
|
36
|
+
client.connectToSyncServer();
|
|
37
|
+
|
|
38
|
+
const group = jazzCloud.node.createGroup();
|
|
39
|
+
const map1 = group.createMap();
|
|
40
|
+
const map2 = group.createMap();
|
|
41
|
+
const map3 = group.createMap();
|
|
42
|
+
|
|
43
|
+
map1.set("key", "value1", "trusting");
|
|
44
|
+
map2.set("key", "value2", "trusting");
|
|
45
|
+
map3.set("key", "value3", "trusting");
|
|
46
|
+
|
|
47
|
+
const [loadedMap1, loadedMap2, loadedMap3] = await Promise.all([
|
|
48
|
+
loadCoValueOrFail(client.node, map1.id),
|
|
49
|
+
loadCoValueOrFail(client.node, map2.id),
|
|
50
|
+
loadCoValueOrFail(client.node, map3.id),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
expect(loadedMap1.get("key")).toEqual("value1");
|
|
54
|
+
expect(loadedMap2.get("key")).toEqual("value2");
|
|
55
|
+
expect(loadedMap3.get("key")).toEqual("value3");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("storage streaming processing", () => {
|
|
60
|
+
test("should process storage streaming callbacks", async () => {
|
|
61
|
+
const client = setupTestNode();
|
|
62
|
+
client.connectToSyncServer();
|
|
63
|
+
const { storage } = client.addStorage();
|
|
64
|
+
|
|
65
|
+
const group = jazzCloud.node.createGroup();
|
|
66
|
+
const map = group.createMap();
|
|
67
|
+
map.set("hello", "world", "trusting");
|
|
68
|
+
|
|
69
|
+
// First load to populate storage
|
|
70
|
+
await loadCoValueOrFail(client.node, map.id);
|
|
71
|
+
|
|
72
|
+
// Restart and load from storage
|
|
73
|
+
client.restart();
|
|
74
|
+
client.connectToSyncServer();
|
|
75
|
+
client.addStorage({ storage });
|
|
76
|
+
|
|
77
|
+
SyncMessagesLog.clear();
|
|
78
|
+
|
|
79
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
80
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
81
|
+
|
|
82
|
+
// Verify content came from storage
|
|
83
|
+
const storageMessages = SyncMessagesLog.messages.filter(
|
|
84
|
+
(msg) => msg.from === "storage" || msg.to === "storage",
|
|
85
|
+
);
|
|
86
|
+
expect(storageMessages.length).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("should invoke streaming callbacks when pulled", async () => {
|
|
90
|
+
const client = setupTestNode();
|
|
91
|
+
const { storage } = client.addStorage();
|
|
92
|
+
|
|
93
|
+
const callback = vi.fn();
|
|
94
|
+
storage.streamingQueue?.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
95
|
+
storage.streamingQueue?.emit();
|
|
96
|
+
|
|
97
|
+
// Wait for processQueues to run
|
|
98
|
+
await waitFor(() => callback.mock.calls.length > 0);
|
|
99
|
+
|
|
100
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should process MEDIUM priority before LOW priority", async () => {
|
|
104
|
+
const client = setupTestNode();
|
|
105
|
+
const { storage } = client.addStorage();
|
|
106
|
+
|
|
107
|
+
const order: string[] = [];
|
|
108
|
+
const lowCallback = () => order.push("low");
|
|
109
|
+
const mediumCallback = () => order.push("medium");
|
|
110
|
+
const highCallback = () => order.push("high");
|
|
111
|
+
|
|
112
|
+
// Push LOW first, then MEDIUM
|
|
113
|
+
storage.streamingQueue?.push(lowCallback, CO_VALUE_PRIORITY.LOW);
|
|
114
|
+
storage.streamingQueue?.push(mediumCallback, CO_VALUE_PRIORITY.MEDIUM);
|
|
115
|
+
storage.streamingQueue?.push(highCallback, CO_VALUE_PRIORITY.HIGH);
|
|
116
|
+
storage.streamingQueue?.emit();
|
|
117
|
+
|
|
118
|
+
// Wait for both to be processed
|
|
119
|
+
await waitFor(() => order.length === 3);
|
|
120
|
+
|
|
121
|
+
// MEDIUM should be processed first
|
|
122
|
+
expect(order).toEqual(["high", "medium", "low"]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("unified scheduling", () => {
|
|
127
|
+
test("should process both incoming messages and storage streaming", async () => {
|
|
128
|
+
const client = setupTestNode();
|
|
129
|
+
client.connectToSyncServer();
|
|
130
|
+
const { storage } = client.addStorage();
|
|
131
|
+
|
|
132
|
+
const group = jazzCloud.node.createGroup();
|
|
133
|
+
const map = group.createMap();
|
|
134
|
+
map.set("hello", "world", "trusting");
|
|
135
|
+
|
|
136
|
+
// Queue a storage streaming callback
|
|
137
|
+
const streamingCallback = vi.fn();
|
|
138
|
+
storage.streamingQueue?.push(streamingCallback, CO_VALUE_PRIORITY.MEDIUM);
|
|
139
|
+
storage.streamingQueue?.emit();
|
|
140
|
+
|
|
141
|
+
// Load from server (incoming messages)
|
|
142
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
143
|
+
|
|
144
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
145
|
+
expect(streamingCallback).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("should alternate between message queue and storage queue", async () => {
|
|
149
|
+
const client = setupTestNode();
|
|
150
|
+
const { storage } = client.addStorage();
|
|
151
|
+
|
|
152
|
+
const order: string[] = [];
|
|
153
|
+
|
|
154
|
+
// Push multiple storage callbacks
|
|
155
|
+
storage.streamingQueue?.push(
|
|
156
|
+
() => order.push("storage1"),
|
|
157
|
+
CO_VALUE_PRIORITY.MEDIUM,
|
|
158
|
+
);
|
|
159
|
+
storage.streamingQueue?.push(
|
|
160
|
+
() => order.push("storage2"),
|
|
161
|
+
CO_VALUE_PRIORITY.MEDIUM,
|
|
162
|
+
);
|
|
163
|
+
storage.streamingQueue?.emit();
|
|
164
|
+
|
|
165
|
+
// Wait for processing
|
|
166
|
+
await waitFor(() => order.length === 2);
|
|
167
|
+
|
|
168
|
+
expect(order).toContain("storage1");
|
|
169
|
+
expect(order).toContain("storage2");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("processing flag", () => {
|
|
174
|
+
test("should prevent concurrent processQueues calls", async () => {
|
|
175
|
+
const client = setupTestNode();
|
|
176
|
+
const { storage } = client.addStorage();
|
|
177
|
+
|
|
178
|
+
let concurrentCalls = 0;
|
|
179
|
+
let maxConcurrentCalls = 0;
|
|
180
|
+
|
|
181
|
+
const callback = () => {
|
|
182
|
+
concurrentCalls++;
|
|
183
|
+
maxConcurrentCalls = Math.max(maxConcurrentCalls, concurrentCalls);
|
|
184
|
+
// Simulate some work
|
|
185
|
+
for (let i = 0; i < 1000; i++) {
|
|
186
|
+
Math.random();
|
|
187
|
+
}
|
|
188
|
+
concurrentCalls--;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Push multiple callbacks
|
|
192
|
+
for (let i = 0; i < 10; i++) {
|
|
193
|
+
storage.streamingQueue?.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Emit multiple times to trigger multiple processQueues calls
|
|
197
|
+
storage.streamingQueue?.emit();
|
|
198
|
+
storage.streamingQueue?.emit();
|
|
199
|
+
storage.streamingQueue?.emit();
|
|
200
|
+
|
|
201
|
+
// Wait for all to complete
|
|
202
|
+
await waitFor(() => storage.streamingQueue?.isEmpty());
|
|
203
|
+
|
|
204
|
+
// Should never have more than 1 concurrent call
|
|
205
|
+
expect(maxConcurrentCalls).toBe(1);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("error handling", () => {
|
|
210
|
+
test("should continue processing after storage callback error", async () => {
|
|
211
|
+
const client = setupTestNode();
|
|
212
|
+
const { storage } = client.addStorage();
|
|
213
|
+
|
|
214
|
+
const processed: string[] = [];
|
|
215
|
+
|
|
216
|
+
storage.streamingQueue?.push(() => {
|
|
217
|
+
processed.push("before");
|
|
218
|
+
}, CO_VALUE_PRIORITY.MEDIUM);
|
|
219
|
+
|
|
220
|
+
storage.streamingQueue?.push(() => {
|
|
221
|
+
throw new Error("Test error");
|
|
222
|
+
}, CO_VALUE_PRIORITY.MEDIUM);
|
|
223
|
+
|
|
224
|
+
storage.streamingQueue?.push(() => {
|
|
225
|
+
processed.push("after");
|
|
226
|
+
}, CO_VALUE_PRIORITY.MEDIUM);
|
|
227
|
+
|
|
228
|
+
storage.streamingQueue?.emit();
|
|
229
|
+
|
|
230
|
+
// Wait for processing to complete
|
|
231
|
+
await waitFor(() => storage.streamingQueue?.isEmpty());
|
|
232
|
+
|
|
233
|
+
// Both before and after should be processed despite error
|
|
234
|
+
expect(processed).toContain("before");
|
|
235
|
+
expect(processed).toContain("after");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("queue triggers", () => {
|
|
240
|
+
test("IncomingMessagesQueue.push should trigger processQueues", async () => {
|
|
241
|
+
const client = setupTestNode();
|
|
242
|
+
client.connectToSyncServer();
|
|
243
|
+
|
|
244
|
+
const group = jazzCloud.node.createGroup();
|
|
245
|
+
const map = group.createMap();
|
|
246
|
+
map.set("hello", "world", "trusting");
|
|
247
|
+
|
|
248
|
+
// Loading should trigger message processing automatically
|
|
249
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
250
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("StorageStreamingQueue.emit should trigger processQueues", async () => {
|
|
254
|
+
const client = setupTestNode();
|
|
255
|
+
const { storage } = client.addStorage();
|
|
256
|
+
|
|
257
|
+
const callback = vi.fn();
|
|
258
|
+
storage.streamingQueue?.push(callback, CO_VALUE_PRIORITY.MEDIUM);
|
|
259
|
+
|
|
260
|
+
// Before emit, callback should not be called
|
|
261
|
+
expect(callback).not.toHaveBeenCalled();
|
|
262
|
+
|
|
263
|
+
// After emit, processQueues should be triggered
|
|
264
|
+
storage.streamingQueue?.emit();
|
|
265
|
+
|
|
266
|
+
await waitFor(() => callback.mock.calls.length > 0);
|
|
267
|
+
expect(callback).toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("setStorage should connect queue listener", async () => {
|
|
271
|
+
const client = setupTestNode();
|
|
272
|
+
|
|
273
|
+
// Before adding storage, there's no queue
|
|
274
|
+
const queueBefore = (
|
|
275
|
+
client.node.syncManager as any
|
|
276
|
+
).getStorageStreamingQueue?.();
|
|
277
|
+
expect(queueBefore).toBeUndefined();
|
|
278
|
+
|
|
279
|
+
// After adding storage, queue should be available
|
|
280
|
+
const { storage } = client.addStorage();
|
|
281
|
+
const queueAfter = (
|
|
282
|
+
client.node.syncManager as any
|
|
283
|
+
).getStorageStreamingQueue?.();
|
|
284
|
+
expect(queueAfter).toBe(storage.streamingQueue);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -36,6 +36,7 @@ function createMockStorage(
|
|
|
36
36
|
) => void;
|
|
37
37
|
store?: (data: any, correctionCallback: any) => void;
|
|
38
38
|
getKnownState?: (id: RawCoID) => any;
|
|
39
|
+
loadKnownState?: (id: string, callback: (knownState: any) => void) => void;
|
|
39
40
|
waitForSync?: (id: string, coValue: any) => Promise<void>;
|
|
40
41
|
trackCoValuesSyncState?: (
|
|
41
42
|
operations: Array<{ id: RawCoID; peerId: PeerID; synced: boolean }>,
|
|
@@ -51,6 +52,8 @@ function createMockStorage(
|
|
|
51
52
|
load: opts.load || vi.fn(),
|
|
52
53
|
store: opts.store || vi.fn(),
|
|
53
54
|
getKnownState: opts.getKnownState || vi.fn(),
|
|
55
|
+
loadKnownState:
|
|
56
|
+
opts.loadKnownState || vi.fn((id, callback) => callback(undefined)),
|
|
54
57
|
waitForSync: opts.waitForSync || vi.fn().mockResolvedValue(undefined),
|
|
55
58
|
trackCoValuesSyncState: opts.trackCoValuesSyncState || vi.fn(),
|
|
56
59
|
getUnsyncedCoValueIDs: opts.getUnsyncedCoValueIDs || vi.fn(),
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { RawCoID, SessionID } from "../ids";
|
|
3
|
+
import { PeerID } from "../sync";
|
|
4
|
+
import { StorageAPI } from "../storage/types";
|
|
5
|
+
import { CoValueKnownState, peerHasAllContent } from "../knownState";
|
|
6
|
+
import { createTestNode, createUnloadedCoValue } from "./testUtils";
|
|
7
|
+
|
|
8
|
+
function createMockStorage(
|
|
9
|
+
opts: {
|
|
10
|
+
load?: (
|
|
11
|
+
id: RawCoID,
|
|
12
|
+
callback: (data: any) => void,
|
|
13
|
+
done: (found: boolean) => void,
|
|
14
|
+
) => void;
|
|
15
|
+
store?: (data: any, correctionCallback: any) => void;
|
|
16
|
+
getKnownState?: (id: RawCoID) => any;
|
|
17
|
+
loadKnownState?: (id: string, callback: (knownState: any) => void) => void;
|
|
18
|
+
waitForSync?: (id: string, coValue: any) => Promise<void>;
|
|
19
|
+
trackCoValuesSyncState?: (
|
|
20
|
+
operations: Array<{ id: RawCoID; peerId: PeerID; synced: boolean }>,
|
|
21
|
+
) => void;
|
|
22
|
+
getUnsyncedCoValueIDs?: (
|
|
23
|
+
callback: (unsyncedCoValueIDs: RawCoID[]) => void,
|
|
24
|
+
) => void;
|
|
25
|
+
stopTrackingSyncState?: (id: RawCoID) => void;
|
|
26
|
+
close?: () => Promise<unknown> | undefined;
|
|
27
|
+
} = {},
|
|
28
|
+
): StorageAPI {
|
|
29
|
+
return {
|
|
30
|
+
load: opts.load || vi.fn(),
|
|
31
|
+
store: opts.store || vi.fn(),
|
|
32
|
+
getKnownState: opts.getKnownState || vi.fn(),
|
|
33
|
+
loadKnownState:
|
|
34
|
+
opts.loadKnownState || vi.fn((id, callback) => callback(undefined)),
|
|
35
|
+
waitForSync: opts.waitForSync || vi.fn().mockResolvedValue(undefined),
|
|
36
|
+
trackCoValuesSyncState: opts.trackCoValuesSyncState || vi.fn(),
|
|
37
|
+
getUnsyncedCoValueIDs: opts.getUnsyncedCoValueIDs || vi.fn(),
|
|
38
|
+
stopTrackingSyncState: opts.stopTrackingSyncState || vi.fn(),
|
|
39
|
+
close: opts.close || vi.fn().mockResolvedValue(undefined),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("peerHasAllContent", () => {
|
|
44
|
+
const storageKnownState: CoValueKnownState = {
|
|
45
|
+
id: "co_test123" as RawCoID,
|
|
46
|
+
header: true,
|
|
47
|
+
sessions: {
|
|
48
|
+
["session1" as SessionID]: 5,
|
|
49
|
+
["session2" as SessionID]: 3,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
test("returns false when peerKnownState is undefined", () => {
|
|
54
|
+
expect(peerHasAllContent(storageKnownState, undefined)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("returns false when peer does not have header but storage does", () => {
|
|
58
|
+
const peerKnownState: CoValueKnownState = {
|
|
59
|
+
id: "co_test123" as RawCoID,
|
|
60
|
+
header: false,
|
|
61
|
+
sessions: {
|
|
62
|
+
["session1" as SessionID]: 5,
|
|
63
|
+
["session2" as SessionID]: 3,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("returns false when peer has fewer transactions in a session", () => {
|
|
70
|
+
const peerKnownState: CoValueKnownState = {
|
|
71
|
+
id: "co_test123" as RawCoID,
|
|
72
|
+
header: true,
|
|
73
|
+
sessions: {
|
|
74
|
+
["session1" as SessionID]: 3, // Less than storage's 5
|
|
75
|
+
["session2" as SessionID]: 3,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("returns false when peer is missing a session", () => {
|
|
82
|
+
const peerKnownState: CoValueKnownState = {
|
|
83
|
+
id: "co_test123" as RawCoID,
|
|
84
|
+
header: true,
|
|
85
|
+
sessions: {
|
|
86
|
+
["session1" as SessionID]: 5,
|
|
87
|
+
// session2 is missing
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("returns true when peer has exactly the same content", () => {
|
|
94
|
+
const peerKnownState: CoValueKnownState = {
|
|
95
|
+
id: "co_test123" as RawCoID,
|
|
96
|
+
header: true,
|
|
97
|
+
sessions: {
|
|
98
|
+
["session1" as SessionID]: 5,
|
|
99
|
+
["session2" as SessionID]: 3,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("returns true when peer has more transactions than storage", () => {
|
|
106
|
+
const peerKnownState: CoValueKnownState = {
|
|
107
|
+
id: "co_test123" as RawCoID,
|
|
108
|
+
header: true,
|
|
109
|
+
sessions: {
|
|
110
|
+
["session1" as SessionID]: 10, // More than storage's 5
|
|
111
|
+
["session2" as SessionID]: 5, // More than storage's 3
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("returns true when peer has additional sessions", () => {
|
|
118
|
+
const peerKnownState: CoValueKnownState = {
|
|
119
|
+
id: "co_test123" as RawCoID,
|
|
120
|
+
header: true,
|
|
121
|
+
sessions: {
|
|
122
|
+
["session1" as SessionID]: 5,
|
|
123
|
+
["session2" as SessionID]: 3,
|
|
124
|
+
["session3" as SessionID]: 2, // Extra session not in storage
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
expect(peerHasAllContent(storageKnownState, peerKnownState)).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("returns true when storage has empty sessions", () => {
|
|
131
|
+
const emptyStorageKnownState: CoValueKnownState = {
|
|
132
|
+
id: "co_test123" as RawCoID,
|
|
133
|
+
header: true,
|
|
134
|
+
sessions: {},
|
|
135
|
+
};
|
|
136
|
+
const peerKnownState: CoValueKnownState = {
|
|
137
|
+
id: "co_test123" as RawCoID,
|
|
138
|
+
header: true,
|
|
139
|
+
sessions: {},
|
|
140
|
+
};
|
|
141
|
+
expect(peerHasAllContent(emptyStorageKnownState, peerKnownState)).toBe(
|
|
142
|
+
true,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("CoValueCore.getKnownStateFromStorage", () => {
|
|
148
|
+
function setup() {
|
|
149
|
+
const node = createTestNode();
|
|
150
|
+
const { coValue, id, header } = createUnloadedCoValue(node);
|
|
151
|
+
return { node, coValue, id, header };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
test("returns undefined when storage is not configured", () => {
|
|
155
|
+
const { coValue } = setup();
|
|
156
|
+
const doneSpy = vi.fn();
|
|
157
|
+
|
|
158
|
+
coValue.getKnownStateFromStorage(doneSpy);
|
|
159
|
+
|
|
160
|
+
expect(doneSpy).toHaveBeenCalledWith(undefined);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("returns current knownState when CoValue is already available", () => {
|
|
164
|
+
const { node, coValue, header } = setup();
|
|
165
|
+
const storage = createMockStorage();
|
|
166
|
+
node.setStorage(storage);
|
|
167
|
+
|
|
168
|
+
// Make the CoValue available by providing header
|
|
169
|
+
coValue.provideHeader(header, undefined, false);
|
|
170
|
+
|
|
171
|
+
const doneSpy = vi.fn();
|
|
172
|
+
coValue.getKnownStateFromStorage(doneSpy);
|
|
173
|
+
|
|
174
|
+
expect(doneSpy).toHaveBeenCalledWith(
|
|
175
|
+
expect.objectContaining({
|
|
176
|
+
header: true,
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("calls storage.loadKnownState when CoValue not in memory", () => {
|
|
182
|
+
const { node, coValue, id } = setup();
|
|
183
|
+
const loadKnownStateSpy = vi.fn((id, callback) => {
|
|
184
|
+
callback({
|
|
185
|
+
id,
|
|
186
|
+
header: true,
|
|
187
|
+
sessions: { session1: 5 },
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
const storage = createMockStorage({ loadKnownState: loadKnownStateSpy });
|
|
191
|
+
node.setStorage(storage);
|
|
192
|
+
|
|
193
|
+
const doneSpy = vi.fn();
|
|
194
|
+
coValue.getKnownStateFromStorage(doneSpy);
|
|
195
|
+
|
|
196
|
+
expect(loadKnownStateSpy).toHaveBeenCalledWith(id, expect.any(Function));
|
|
197
|
+
expect(doneSpy).toHaveBeenCalledWith({
|
|
198
|
+
id,
|
|
199
|
+
header: true,
|
|
200
|
+
sessions: { session1: 5 },
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("returns undefined when storage does not have the CoValue", () => {
|
|
205
|
+
const { node, coValue } = setup();
|
|
206
|
+
const loadKnownStateSpy = vi.fn((id, callback) => {
|
|
207
|
+
callback(undefined);
|
|
208
|
+
});
|
|
209
|
+
const storage = createMockStorage({ loadKnownState: loadKnownStateSpy });
|
|
210
|
+
node.setStorage(storage);
|
|
211
|
+
|
|
212
|
+
const doneSpy = vi.fn();
|
|
213
|
+
coValue.getKnownStateFromStorage(doneSpy);
|
|
214
|
+
|
|
215
|
+
expect(doneSpy).toHaveBeenCalledWith(undefined);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CoValueCore, LocalNode } from "../exports";
|
|
2
2
|
import { NewContentMessage, SyncMessage } from "../sync";
|
|
3
3
|
import { CoValueKnownState } from "../knownState.js";
|
|
4
|
+
import { LazyLoadMessage, LazyLoadResultMessage } from "./testUtils.js";
|
|
4
5
|
|
|
5
6
|
function simplifySessions(msg: Pick<CoValueKnownState, "sessions" | "header">) {
|
|
6
7
|
const count = Object.values(msg.sessions).reduce(
|
|
@@ -25,12 +26,14 @@ function simplifyNewContent(content: NewContentMessage["new"]) {
|
|
|
25
26
|
.join(" | ");
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
type TestMessage = SyncMessage | LazyLoadMessage | LazyLoadResultMessage;
|
|
30
|
+
|
|
28
31
|
export function toSimplifiedMessages(
|
|
29
32
|
coValues: Record<string, CoValueCore>,
|
|
30
33
|
messages: {
|
|
31
34
|
from: string;
|
|
32
35
|
to: string;
|
|
33
|
-
msg:
|
|
36
|
+
msg: TestMessage;
|
|
34
37
|
}[],
|
|
35
38
|
) {
|
|
36
39
|
function getCoValue(id: string) {
|
|
@@ -43,7 +46,7 @@ export function toSimplifiedMessages(
|
|
|
43
46
|
return `unknown/${id}`;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
function toDebugString(from: string, to: string, msg:
|
|
49
|
+
function toDebugString(from: string, to: string, msg: TestMessage) {
|
|
47
50
|
switch (msg.action) {
|
|
48
51
|
case "known":
|
|
49
52
|
return `${from} -> ${to} | KNOWN ${msg.isCorrection ? "CORRECTION " : ""}${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
@@ -53,6 +56,10 @@ export function toSimplifiedMessages(
|
|
|
53
56
|
return `${from} -> ${to} | DONE ${getCoValue(msg.id)}`;
|
|
54
57
|
case "content":
|
|
55
58
|
return `${from} -> ${to} | CONTENT ${getCoValue(msg.id)} header: ${Boolean(msg.header)} new: ${simplifyNewContent(msg.new)}${msg.expectContentUntil ? ` expectContentUntil: ${simplifySessions({ sessions: msg.expectContentUntil, header: true })}` : ""}`;
|
|
59
|
+
case "lazyLoad":
|
|
60
|
+
return `${from} -> ${to} | GET_KNOWN_STATE ${getCoValue(msg.id)}`;
|
|
61
|
+
case "lazyLoadResult":
|
|
62
|
+
return `${from} -> ${to} | GET_KNOWN_STATE_RESULT ${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -75,7 +82,7 @@ export function debugMessages(
|
|
|
75
82
|
messages: {
|
|
76
83
|
from: string;
|
|
77
84
|
to: string;
|
|
78
|
-
msg:
|
|
85
|
+
msg: TestMessage;
|
|
79
86
|
}[],
|
|
80
87
|
) {
|
|
81
88
|
console.log(toSimplifiedMessages(coValues, messages));
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { setGarbageCollectorMaxAge } from "../config";
|
|
4
|
-
import { emptyKnownState } from "../exports";
|
|
5
4
|
import {
|
|
6
5
|
SyncMessagesLog,
|
|
7
6
|
TEST_NODE_CONFIG,
|
|
8
7
|
loadCoValueOrFail,
|
|
9
8
|
setupTestNode,
|
|
10
|
-
waitFor,
|
|
11
9
|
} from "./testUtils";
|
|
12
10
|
|
|
13
11
|
// We want to simulate a real world communication that happens asynchronously
|