cojson 0.19.18 → 0.19.20
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 +9 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/SyncStateManager.d.ts +5 -2
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +49 -12
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/UnsyncedCoValuesTracker.d.ts +81 -0
- package/dist/UnsyncedCoValuesTracker.d.ts.map +1 -0
- package/dist/UnsyncedCoValuesTracker.js +209 -0
- package/dist/UnsyncedCoValuesTracker.js.map +1 -0
- 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 +11 -3
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +6 -1
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +9 -5
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +12 -8
- 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 +1 -1
- package/dist/storage/knownState.js +4 -4
- package/dist/storage/sqlite/client.d.ts +8 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +17 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +9 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +8 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +19 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +9 -2
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +9 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +17 -4
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +67 -44
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +35 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts +38 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +181 -7
- 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/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/SyncStateManager.test.js +3 -3
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.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.mesh.test.js +19 -19
- 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/sync.tracking.test.d.ts +2 -0
- package/dist/tests/sync.tracking.test.d.ts.map +1 -0
- package/dist/tests/sync.tracking.test.js +261 -0
- package/dist/tests/sync.tracking.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +4 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +4 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +4 -4
- package/src/PeerState.ts +2 -2
- package/src/SyncStateManager.ts +63 -12
- package/src/UnsyncedCoValuesTracker.ts +272 -0
- package/src/config.ts +13 -0
- package/src/exports.ts +10 -1
- package/src/localNode.ts +15 -3
- 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 +4 -4
- package/src/storage/sqlite/client.ts +31 -0
- package/src/storage/sqlite/sqliteMigrations.ts +9 -0
- package/src/storage/sqliteAsync/client.ts +35 -0
- package/src/storage/storageAsync.ts +18 -1
- package/src/storage/storageSync.ts +119 -56
- package/src/storage/types.ts +42 -0
- package/src/sync.ts +235 -8
- package/src/tests/IncomingMessagesQueue.test.ts +4 -206
- package/src/tests/StorageStreamingQueue.test.ts +276 -0
- package/src/tests/SyncManager.processQueues.test.ts +287 -0
- package/src/tests/SyncStateManager.test.ts +3 -0
- package/src/tests/coValueCore.loadFromStorage.test.ts +11 -0
- package/src/tests/setup.ts +4 -0
- package/src/tests/sync.garbageCollection.test.ts +1 -3
- package/src/tests/sync.mesh.test.ts +19 -19
- package/src/tests/sync.storage.test.ts +224 -32
- package/src/tests/sync.test.ts +1 -9
- package/src/tests/sync.tracking.test.ts +396 -0
- package/src/tests/testUtils.ts +11 -5
- package/vitest.config.ts +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { setSyncStateTrackingBatchDelay } from "../UnsyncedCoValuesTracker";
|
|
3
|
+
import {
|
|
4
|
+
blockMessageTypeOnOutgoingPeer,
|
|
5
|
+
SyncMessagesLog,
|
|
6
|
+
TEST_NODE_CONFIG,
|
|
7
|
+
setupTestNode,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "./testUtils";
|
|
10
|
+
|
|
11
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
15
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
16
|
+
|
|
17
|
+
SyncMessagesLog.clear();
|
|
18
|
+
jazzCloud = setupTestNode({ isSyncServer: true });
|
|
19
|
+
|
|
20
|
+
setSyncStateTrackingBatchDelay(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
setSyncStateTrackingBatchDelay(1000);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("coValue sync state tracking", () => {
|
|
28
|
+
test("coValues with unsynced local changes are tracked as unsynced", async () => {
|
|
29
|
+
const { node: client } = setupTestNode({ connected: true });
|
|
30
|
+
|
|
31
|
+
const group = client.createGroup();
|
|
32
|
+
const map = group.createMap();
|
|
33
|
+
map.set("key", "value");
|
|
34
|
+
|
|
35
|
+
// Wait for local transaction to trigger sync
|
|
36
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
37
|
+
|
|
38
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
39
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("coValue is marked as synced when all persistent server peers have received the content", async () => {
|
|
43
|
+
const { node: client } = setupTestNode({ connected: true });
|
|
44
|
+
|
|
45
|
+
const group = client.createGroup();
|
|
46
|
+
const map = group.createMap();
|
|
47
|
+
map.set("key", "value");
|
|
48
|
+
|
|
49
|
+
// Wait for local transaction to trigger sync
|
|
50
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
51
|
+
|
|
52
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
53
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
54
|
+
|
|
55
|
+
const serverPeer =
|
|
56
|
+
client.syncManager.peers[jazzCloud.node.currentSessionID]!;
|
|
57
|
+
await waitFor(() =>
|
|
58
|
+
client.syncManager.syncState.isSynced(serverPeer, map.id),
|
|
59
|
+
);
|
|
60
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("coValues are tracked as unsynced even if there are no persistent server peers", async () => {
|
|
64
|
+
const { node: client } = setupTestNode({ connected: false });
|
|
65
|
+
|
|
66
|
+
const group = client.createGroup();
|
|
67
|
+
const map = group.createMap();
|
|
68
|
+
map.set("key", "value");
|
|
69
|
+
|
|
70
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
71
|
+
|
|
72
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
73
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("only tracks sync state for persistent servers peers", async () => {
|
|
77
|
+
const { node: client, connectToSyncServer } = setupTestNode({
|
|
78
|
+
connected: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Add a second server peer that is NOT persistent
|
|
82
|
+
const server2 = setupTestNode({ isSyncServer: true });
|
|
83
|
+
const { peer: server2PeerOnClient, peerState: server2PeerStateOnClient } =
|
|
84
|
+
connectToSyncServer({
|
|
85
|
+
syncServer: server2.node,
|
|
86
|
+
syncServerName: "server2",
|
|
87
|
+
persistent: false,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Do not deliver new content messages to the second server peer
|
|
91
|
+
blockMessageTypeOnOutgoingPeer(server2PeerOnClient, "content", {});
|
|
92
|
+
|
|
93
|
+
const group = client.createGroup();
|
|
94
|
+
const map = group.createMap();
|
|
95
|
+
map.set("key", "value");
|
|
96
|
+
|
|
97
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
98
|
+
|
|
99
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
100
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
101
|
+
|
|
102
|
+
const serverPeer =
|
|
103
|
+
client.syncManager.peers[jazzCloud.node.currentSessionID]!;
|
|
104
|
+
await waitFor(() =>
|
|
105
|
+
client.syncManager.syncState.isSynced(serverPeer, map.id),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(
|
|
109
|
+
client.syncManager.syncState.isSynced(server2PeerStateOnClient, map.id),
|
|
110
|
+
).toBe(false);
|
|
111
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("coValues are not tracked as unsynced if sync is disabled", async () => {
|
|
115
|
+
const { node: client } = setupTestNode({
|
|
116
|
+
connected: false,
|
|
117
|
+
syncWhen: "never",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const group = client.createGroup();
|
|
121
|
+
const map = group.createMap();
|
|
122
|
+
map.set("key", "value");
|
|
123
|
+
|
|
124
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
125
|
+
|
|
126
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
127
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("already synced coValues are not tracked as unsynced when trackSyncState is called", async () => {
|
|
131
|
+
const { node: client } = setupTestNode({ connected: true });
|
|
132
|
+
|
|
133
|
+
const group = client.createGroup();
|
|
134
|
+
const map = group.createMap();
|
|
135
|
+
map.set("key", "value");
|
|
136
|
+
|
|
137
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
138
|
+
|
|
139
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
140
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
141
|
+
|
|
142
|
+
const serverPeer =
|
|
143
|
+
client.syncManager.peers[jazzCloud.node.currentSessionID]!;
|
|
144
|
+
await waitFor(() =>
|
|
145
|
+
client.syncManager.syncState.isSynced(serverPeer, map.id),
|
|
146
|
+
);
|
|
147
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
148
|
+
|
|
149
|
+
// @ts-expect-error trackSyncState is private
|
|
150
|
+
client.syncManager.trackSyncState(map.id);
|
|
151
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("imported coValue content is tracked as unsynced", async () => {
|
|
155
|
+
const { node: client } = setupTestNode({ connected: true });
|
|
156
|
+
const { node: client2 } = setupTestNode({ connected: false });
|
|
157
|
+
|
|
158
|
+
const group = client2.createGroup();
|
|
159
|
+
const map = group.createMap();
|
|
160
|
+
map.set("key", "value");
|
|
161
|
+
|
|
162
|
+
// Export the content from client2 to client
|
|
163
|
+
const groupContent = group.core.newContentSince()![0]!;
|
|
164
|
+
const mapContent = map.core.newContentSince()![0]!;
|
|
165
|
+
client.syncManager.handleNewContent(groupContent, "import");
|
|
166
|
+
client.syncManager.handleNewContent(mapContent, "import");
|
|
167
|
+
|
|
168
|
+
const unsyncedTracker = client.syncManager.unsyncedTracker;
|
|
169
|
+
|
|
170
|
+
// The imported coValue should be tracked as unsynced since it hasn't been synced to the server yet
|
|
171
|
+
expect(unsyncedTracker.has(group.id)).toBe(true);
|
|
172
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
173
|
+
|
|
174
|
+
// Wait for the map to sync
|
|
175
|
+
const serverPeer =
|
|
176
|
+
client.syncManager.peers[jazzCloud.node.currentSessionID]!;
|
|
177
|
+
await waitFor(() =>
|
|
178
|
+
client.syncManager.syncState.isSynced(serverPeer, map.id),
|
|
179
|
+
);
|
|
180
|
+
expect(unsyncedTracker.has(map.id)).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("sync state persistence", () => {
|
|
185
|
+
test("unsynced coValues are asynchronously persisted to storage", async () => {
|
|
186
|
+
const { node: client, addStorage } = setupTestNode({ connected: false });
|
|
187
|
+
addStorage();
|
|
188
|
+
|
|
189
|
+
const group = client.createGroup();
|
|
190
|
+
const map = group.createMap();
|
|
191
|
+
map.set("key", "value");
|
|
192
|
+
|
|
193
|
+
// Wait for the unsynced coValues to be persisted to storage
|
|
194
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
195
|
+
|
|
196
|
+
const unsyncedCoValueIDs = await new Promise((resolve) =>
|
|
197
|
+
client.storage?.getUnsyncedCoValueIDs(resolve),
|
|
198
|
+
);
|
|
199
|
+
expect(unsyncedCoValueIDs).toHaveLength(2);
|
|
200
|
+
expect(unsyncedCoValueIDs).toContain(map.id);
|
|
201
|
+
expect(unsyncedCoValueIDs).toContain(group.id);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("synced coValues are removed from storage", async () => {
|
|
205
|
+
const { node: client, addStorage } = setupTestNode({ connected: true });
|
|
206
|
+
addStorage();
|
|
207
|
+
|
|
208
|
+
const group = client.createGroup();
|
|
209
|
+
const map = group.createMap();
|
|
210
|
+
map.set("key", "value");
|
|
211
|
+
|
|
212
|
+
// Wait enough time for the coValue to be synced
|
|
213
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
214
|
+
|
|
215
|
+
const unsyncedCoValueIDs = await new Promise((resolve) =>
|
|
216
|
+
client.storage?.getUnsyncedCoValueIDs(resolve),
|
|
217
|
+
);
|
|
218
|
+
expect(unsyncedCoValueIDs).toHaveLength(0);
|
|
219
|
+
expect(client.syncManager.unsyncedTracker.has(map.id)).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("unsynced coValues are persisted to storage when the node is shutdown", async () => {
|
|
223
|
+
const { node: client, addStorage } = setupTestNode({ connected: false });
|
|
224
|
+
addStorage();
|
|
225
|
+
|
|
226
|
+
const group = client.createGroup();
|
|
227
|
+
const map = group.createMap();
|
|
228
|
+
map.set("key", "value");
|
|
229
|
+
|
|
230
|
+
// Wait for local transaction to trigger sync
|
|
231
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
|
232
|
+
|
|
233
|
+
await client.gracefulShutdown();
|
|
234
|
+
|
|
235
|
+
const unsyncedCoValueIDs = await new Promise((resolve) =>
|
|
236
|
+
client.storage?.getUnsyncedCoValueIDs(resolve),
|
|
237
|
+
);
|
|
238
|
+
expect(unsyncedCoValueIDs).toHaveLength(2);
|
|
239
|
+
expect(unsyncedCoValueIDs).toContain(map.id);
|
|
240
|
+
expect(unsyncedCoValueIDs).toContain(group.id);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("sync resumption", () => {
|
|
245
|
+
test("unsynced coValues are resumed when the node is restarted", async () => {
|
|
246
|
+
const client = setupTestNode({ connected: false });
|
|
247
|
+
const { storage } = client.addStorage();
|
|
248
|
+
|
|
249
|
+
const getUnsyncedCoValueIDsFromStorage = async () =>
|
|
250
|
+
new Promise<string[]>((resolve) =>
|
|
251
|
+
client.node.storage?.getUnsyncedCoValueIDs(resolve),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const group = client.node.createGroup();
|
|
255
|
+
const map = group.createMap();
|
|
256
|
+
map.set("key", "value");
|
|
257
|
+
|
|
258
|
+
// Wait for the unsynced coValues to be persisted to storage
|
|
259
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
260
|
+
|
|
261
|
+
const unsyncedTracker = client.node.syncManager.unsyncedTracker;
|
|
262
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
263
|
+
expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(2);
|
|
264
|
+
|
|
265
|
+
client.restart();
|
|
266
|
+
client.addStorage({ storage });
|
|
267
|
+
const { peerState: serverPeerState } = client.connectToSyncServer();
|
|
268
|
+
|
|
269
|
+
// Wait for sync to resume & complete
|
|
270
|
+
await waitFor(
|
|
271
|
+
async () => (await getUnsyncedCoValueIDsFromStorage()).length === 0,
|
|
272
|
+
);
|
|
273
|
+
expect(
|
|
274
|
+
client.node.syncManager.syncState.isSynced(serverPeerState, map.id),
|
|
275
|
+
).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("lots of unsynced coValues are resumed in batches when the node is restarted", async () => {
|
|
279
|
+
const client = setupTestNode({ connected: false });
|
|
280
|
+
const { storage } = client.addStorage();
|
|
281
|
+
|
|
282
|
+
const getUnsyncedCoValueIDsFromStorage = async () =>
|
|
283
|
+
new Promise<string[]>((resolve) =>
|
|
284
|
+
client.node.storage?.getUnsyncedCoValueIDs(resolve),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const group = client.node.createGroup();
|
|
288
|
+
const maps = Array.from({ length: 100 }, () => {
|
|
289
|
+
const map = group.createMap();
|
|
290
|
+
map.set("key", "value");
|
|
291
|
+
return map;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Wait for the unsynced coValues to be persisted to storage
|
|
295
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
296
|
+
|
|
297
|
+
const unsyncedTracker = client.node.syncManager.unsyncedTracker;
|
|
298
|
+
for (const map of maps) {
|
|
299
|
+
expect(unsyncedTracker.has(map.id)).toBe(true);
|
|
300
|
+
}
|
|
301
|
+
expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(101);
|
|
302
|
+
|
|
303
|
+
client.restart();
|
|
304
|
+
client.addStorage({ storage });
|
|
305
|
+
const { peerState: serverPeerState } = client.connectToSyncServer();
|
|
306
|
+
|
|
307
|
+
// Wait for sync to resume & complete
|
|
308
|
+
await waitFor(
|
|
309
|
+
async () => (await getUnsyncedCoValueIDsFromStorage()).length === 0,
|
|
310
|
+
);
|
|
311
|
+
for (const map of maps) {
|
|
312
|
+
expect(
|
|
313
|
+
client.node.syncManager.syncState.isSynced(serverPeerState, map.id),
|
|
314
|
+
).toBe(true);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("old peer entries are removed from storage when restarting with new peers", async () => {
|
|
319
|
+
const client = setupTestNode();
|
|
320
|
+
const { peer: serverPeer } = client.connectToSyncServer({
|
|
321
|
+
persistent: true,
|
|
322
|
+
});
|
|
323
|
+
const { storage } = client.addStorage();
|
|
324
|
+
|
|
325
|
+
// Do not deliver new content messages to the sync server
|
|
326
|
+
blockMessageTypeOnOutgoingPeer(serverPeer, "content", {});
|
|
327
|
+
|
|
328
|
+
const getUnsyncedCoValueIDsFromStorage = async () =>
|
|
329
|
+
new Promise<string[]>((resolve) =>
|
|
330
|
+
client.node.storage?.getUnsyncedCoValueIDs(resolve),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const group = client.node.createGroup();
|
|
334
|
+
const map = group.createMap();
|
|
335
|
+
map.set("key", "value");
|
|
336
|
+
|
|
337
|
+
// Wait for the unsynced coValues to be persisted to storage
|
|
338
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
339
|
+
|
|
340
|
+
expect(await getUnsyncedCoValueIDsFromStorage()).toHaveLength(2);
|
|
341
|
+
|
|
342
|
+
client.restart();
|
|
343
|
+
client.addStorage({ storage });
|
|
344
|
+
const newSyncServer = setupTestNode({ isSyncServer: true });
|
|
345
|
+
const { peerState: newServerPeerState } = client.connectToSyncServer({
|
|
346
|
+
syncServer: newSyncServer.node,
|
|
347
|
+
persistent: true,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Wait for sync to resume & complete
|
|
351
|
+
await waitFor(
|
|
352
|
+
async () => (await getUnsyncedCoValueIDsFromStorage()).length === 0,
|
|
353
|
+
);
|
|
354
|
+
expect(
|
|
355
|
+
client.node.syncManager.syncState.isSynced(newServerPeerState, map.id),
|
|
356
|
+
).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("sync resumption is skipped when adding a peer that is not a persistent server", async () => {
|
|
360
|
+
const client = setupTestNode({ connected: false });
|
|
361
|
+
const { storage } = client.addStorage();
|
|
362
|
+
|
|
363
|
+
const getUnsyncedCoValueIDsFromStorage = async () =>
|
|
364
|
+
new Promise<string[]>((resolve) =>
|
|
365
|
+
client.node.storage?.getUnsyncedCoValueIDs(resolve),
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
const group = client.node.createGroup();
|
|
369
|
+
const map = group.createMap();
|
|
370
|
+
map.set("key", "value");
|
|
371
|
+
|
|
372
|
+
// Wait for the unsynced coValues to be persisted to storage
|
|
373
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
374
|
+
|
|
375
|
+
let unsyncedCoValueIDs = await getUnsyncedCoValueIDsFromStorage();
|
|
376
|
+
expect(unsyncedCoValueIDs).toHaveLength(2);
|
|
377
|
+
expect(unsyncedCoValueIDs).toContain(map.id);
|
|
378
|
+
expect(unsyncedCoValueIDs).toContain(group.id);
|
|
379
|
+
|
|
380
|
+
client.restart();
|
|
381
|
+
client.addStorage({ storage });
|
|
382
|
+
const newPeer = setupTestNode({ isSyncServer: true });
|
|
383
|
+
client.connectToSyncServer({
|
|
384
|
+
syncServer: newPeer.node,
|
|
385
|
+
persistent: false,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Wait to confirm sync is not resumed
|
|
389
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
390
|
+
|
|
391
|
+
unsyncedCoValueIDs = await getUnsyncedCoValueIDsFromStorage();
|
|
392
|
+
expect(unsyncedCoValueIDs).toHaveLength(2);
|
|
393
|
+
expect(unsyncedCoValueIDs).toContain(map.id);
|
|
394
|
+
expect(unsyncedCoValueIDs).toContain(group.id);
|
|
395
|
+
});
|
|
396
|
+
});
|
package/src/tests/testUtils.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import type { SessionID } from "../ids.js";
|
|
23
23
|
import { LocalNode } from "../localNode.js";
|
|
24
24
|
import { connectedPeers } from "../streamUtils.js";
|
|
25
|
-
import type { Peer, SyncMessage } from "../sync.js";
|
|
25
|
+
import type { Peer, SyncMessage, SyncWhen } from "../sync.js";
|
|
26
26
|
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
27
27
|
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
|
28
28
|
import { createAsyncStorage, createSyncStorage } from "./testStorage.js";
|
|
@@ -449,13 +449,14 @@ export function setupTestNode(
|
|
|
449
449
|
isSyncServer?: boolean;
|
|
450
450
|
connected?: boolean;
|
|
451
451
|
secret?: AgentSecret;
|
|
452
|
+
syncWhen?: SyncWhen;
|
|
452
453
|
} = {},
|
|
453
454
|
) {
|
|
454
455
|
const [admin, session] = opts.secret
|
|
455
456
|
? agentAndSessionIDFromSecret(opts.secret)
|
|
456
457
|
: randomAgentAndSessionID();
|
|
457
458
|
|
|
458
|
-
let node = new LocalNode(admin.agentSecret, session, Crypto);
|
|
459
|
+
let node = new LocalNode(admin.agentSecret, session, Crypto, opts.syncWhen);
|
|
459
460
|
|
|
460
461
|
if (opts.isSyncServer) {
|
|
461
462
|
syncServer.current = node;
|
|
@@ -527,7 +528,12 @@ export function setupTestNode(
|
|
|
527
528
|
addAsyncStorage,
|
|
528
529
|
restart: () => {
|
|
529
530
|
node.gracefulShutdown();
|
|
530
|
-
ctx.node = node = new LocalNode(
|
|
531
|
+
ctx.node = node = new LocalNode(
|
|
532
|
+
admin.agentSecret,
|
|
533
|
+
session,
|
|
534
|
+
Crypto,
|
|
535
|
+
opts.syncWhen,
|
|
536
|
+
);
|
|
531
537
|
|
|
532
538
|
if (opts.isSyncServer) {
|
|
533
539
|
syncServer.current = node;
|
|
@@ -646,8 +652,8 @@ export async function setupTestAccount(
|
|
|
646
652
|
connectToSyncServer();
|
|
647
653
|
}
|
|
648
654
|
|
|
649
|
-
onTestFinished(() => {
|
|
650
|
-
ctx.node.gracefulShutdown();
|
|
655
|
+
onTestFinished(async () => {
|
|
656
|
+
await ctx.node.gracefulShutdown();
|
|
651
657
|
});
|
|
652
658
|
|
|
653
659
|
return {
|