cojson 0.8.19 → 0.8.23
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/CHANGELOG.md +13 -0
- package/dist/native/CoValuesStore.js +31 -0
- package/dist/native/CoValuesStore.js.map +1 -0
- package/dist/native/PeerState.js +7 -0
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/SyncStateSubscriptionManager.js +2 -2
- package/dist/native/SyncStateSubscriptionManager.js.map +1 -1
- package/dist/native/coValueState.js +175 -27
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/localNode.js +20 -41
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/sync.js +49 -93
- package/dist/native/sync.js.map +1 -1
- package/dist/web/CoValuesStore.js +31 -0
- package/dist/web/CoValuesStore.js.map +1 -0
- package/dist/web/PeerState.js +7 -0
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/SyncStateSubscriptionManager.js +2 -2
- package/dist/web/SyncStateSubscriptionManager.js.map +1 -1
- package/dist/web/coValueState.js +175 -27
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/localNode.js +20 -41
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/sync.js +49 -93
- package/dist/web/sync.js.map +1 -1
- package/package.json +1 -1
- package/src/CoValuesStore.ts +38 -0
- package/src/PeerKnownStates.ts +1 -1
- package/src/PeerState.ts +10 -1
- package/src/SyncStateSubscriptionManager.ts +2 -2
- package/src/coValueState.ts +253 -42
- package/src/localNode.ts +28 -56
- package/src/sync.ts +64 -116
- package/src/tests/PeerState.test.ts +44 -1
- package/src/tests/coValueState.test.ts +362 -0
- package/src/tests/group.test.ts +1 -1
- package/src/tests/sync.test.ts +140 -19
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { PeerState } from "../PeerState";
|
|
3
|
+
import { CoValueCore } from "../coValueCore";
|
|
4
|
+
import { CO_VALUE_LOADING_MAX_RETRIES, CoValueState } from "../coValueState";
|
|
5
|
+
import { RawCoID } from "../ids";
|
|
6
|
+
import { Peer } from "../sync";
|
|
7
|
+
|
|
8
|
+
describe("CoValueState", () => {
|
|
9
|
+
const mockCoValueId = "co_test123" as RawCoID;
|
|
10
|
+
|
|
11
|
+
test("should create unknown state", () => {
|
|
12
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
13
|
+
|
|
14
|
+
expect(state.id).toBe(mockCoValueId);
|
|
15
|
+
expect(state.state.type).toBe("unknown");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("should create loading state", () => {
|
|
19
|
+
const peerIds = ["peer1", "peer2"];
|
|
20
|
+
const state = CoValueState.Loading(mockCoValueId, peerIds);
|
|
21
|
+
|
|
22
|
+
expect(state.id).toBe(mockCoValueId);
|
|
23
|
+
expect(state.state.type).toBe("loading");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("should create available state", async () => {
|
|
27
|
+
const mockCoValue = { id: mockCoValueId } as CoValueCore;
|
|
28
|
+
const state = CoValueState.Available(mockCoValue);
|
|
29
|
+
|
|
30
|
+
expect(state.id).toBe(mockCoValueId);
|
|
31
|
+
expect(state.state.type).toBe("available");
|
|
32
|
+
expect((state.state as any).coValue).toBe(mockCoValue);
|
|
33
|
+
await expect(state.getCoValue()).resolves.toEqual(mockCoValue);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("should handle found action", async () => {
|
|
37
|
+
const mockCoValue = { id: mockCoValueId } as CoValueCore;
|
|
38
|
+
const state = CoValueState.Loading(mockCoValueId, ["peer1", "peer2"]);
|
|
39
|
+
|
|
40
|
+
const stateValuePromise = state.getCoValue();
|
|
41
|
+
|
|
42
|
+
state.dispatch({
|
|
43
|
+
type: "available",
|
|
44
|
+
coValue: mockCoValue,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = await state.getCoValue();
|
|
48
|
+
expect(result).toBe(mockCoValue);
|
|
49
|
+
await expect(stateValuePromise).resolves.toBe(mockCoValue);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should ignore actions when not in loading state", () => {
|
|
53
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
54
|
+
|
|
55
|
+
state.dispatch({
|
|
56
|
+
type: "not-found-in-peer",
|
|
57
|
+
peerId: "peer1",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(state.state.type).toBe("unknown");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("should retry loading from peers when unsuccessful", async () => {
|
|
64
|
+
vi.useFakeTimers();
|
|
65
|
+
|
|
66
|
+
const peer1 = createMockPeerState(
|
|
67
|
+
{
|
|
68
|
+
id: "peer1",
|
|
69
|
+
role: "server",
|
|
70
|
+
},
|
|
71
|
+
async () => {
|
|
72
|
+
state.dispatch({
|
|
73
|
+
type: "not-found-in-peer",
|
|
74
|
+
peerId: "peer1",
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
const peer2 = createMockPeerState(
|
|
79
|
+
{
|
|
80
|
+
id: "peer2",
|
|
81
|
+
role: "server",
|
|
82
|
+
},
|
|
83
|
+
async () => {
|
|
84
|
+
state.dispatch({
|
|
85
|
+
type: "not-found-in-peer",
|
|
86
|
+
peerId: "peer2",
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
const mockPeers = [peer1, peer2] as unknown as PeerState[];
|
|
91
|
+
|
|
92
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
93
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
94
|
+
|
|
95
|
+
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
|
|
96
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
|
|
97
|
+
await vi.runAllTimersAsync();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await loadPromise;
|
|
101
|
+
|
|
102
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
|
|
103
|
+
CO_VALUE_LOADING_MAX_RETRIES,
|
|
104
|
+
);
|
|
105
|
+
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
|
|
106
|
+
CO_VALUE_LOADING_MAX_RETRIES,
|
|
107
|
+
);
|
|
108
|
+
expect(state.state.type).toBe("unavailable");
|
|
109
|
+
await expect(state.getCoValue()).resolves.toBe("unavailable");
|
|
110
|
+
|
|
111
|
+
vi.useRealTimers();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should skip errored coValues when loading from peers", async () => {
|
|
115
|
+
vi.useFakeTimers();
|
|
116
|
+
|
|
117
|
+
const peer1 = createMockPeerState(
|
|
118
|
+
{
|
|
119
|
+
id: "peer1",
|
|
120
|
+
role: "server",
|
|
121
|
+
},
|
|
122
|
+
async () => {
|
|
123
|
+
peer1.erroredCoValues.set(mockCoValueId, new Error("test") as any);
|
|
124
|
+
state.dispatch({
|
|
125
|
+
type: "not-found-in-peer",
|
|
126
|
+
peerId: "peer1",
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
const peer2 = createMockPeerState(
|
|
131
|
+
{
|
|
132
|
+
id: "peer2",
|
|
133
|
+
role: "server",
|
|
134
|
+
},
|
|
135
|
+
async () => {
|
|
136
|
+
state.dispatch({
|
|
137
|
+
type: "not-found-in-peer",
|
|
138
|
+
peerId: "peer2",
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const mockPeers = [peer1, peer2] as unknown as PeerState[];
|
|
144
|
+
|
|
145
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
146
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
147
|
+
|
|
148
|
+
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
|
|
149
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
|
|
150
|
+
await vi.runAllTimersAsync();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await loadPromise;
|
|
154
|
+
|
|
155
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
|
|
156
|
+
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
|
|
157
|
+
CO_VALUE_LOADING_MAX_RETRIES,
|
|
158
|
+
);
|
|
159
|
+
expect(state.state.type).toBe("unavailable");
|
|
160
|
+
await expect(state.getCoValue()).resolves.toBe("unavailable");
|
|
161
|
+
|
|
162
|
+
vi.useRealTimers();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("should retry only on server peers", async () => {
|
|
166
|
+
vi.useFakeTimers();
|
|
167
|
+
|
|
168
|
+
const peer1 = createMockPeerState(
|
|
169
|
+
{
|
|
170
|
+
id: "peer1",
|
|
171
|
+
role: "storage",
|
|
172
|
+
},
|
|
173
|
+
async () => {
|
|
174
|
+
state.dispatch({
|
|
175
|
+
type: "not-found-in-peer",
|
|
176
|
+
peerId: "peer1",
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
const peer2 = createMockPeerState(
|
|
181
|
+
{
|
|
182
|
+
id: "peer2",
|
|
183
|
+
role: "server",
|
|
184
|
+
},
|
|
185
|
+
async () => {
|
|
186
|
+
state.dispatch({
|
|
187
|
+
type: "not-found-in-peer",
|
|
188
|
+
peerId: "peer2",
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
const mockPeers = [peer1, peer2] as unknown as PeerState[];
|
|
193
|
+
|
|
194
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
195
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
196
|
+
|
|
197
|
+
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
|
|
198
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
|
|
199
|
+
await vi.runAllTimersAsync();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await loadPromise;
|
|
203
|
+
|
|
204
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
|
|
206
|
+
CO_VALUE_LOADING_MAX_RETRIES,
|
|
207
|
+
);
|
|
208
|
+
expect(state.state.type).toBe("unavailable");
|
|
209
|
+
await expect(state.getCoValue()).resolves.toEqual("unavailable");
|
|
210
|
+
|
|
211
|
+
vi.useRealTimers();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("should handle the coValues that become available in between of the retries", async () => {
|
|
215
|
+
vi.useFakeTimers();
|
|
216
|
+
|
|
217
|
+
let retries = 0;
|
|
218
|
+
|
|
219
|
+
const peer1 = createMockPeerState(
|
|
220
|
+
{
|
|
221
|
+
id: "peer1",
|
|
222
|
+
role: "server",
|
|
223
|
+
},
|
|
224
|
+
async () => {
|
|
225
|
+
retries++;
|
|
226
|
+
state.dispatch({
|
|
227
|
+
type: "not-found-in-peer",
|
|
228
|
+
peerId: "peer1",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (retries === 2) {
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
state.dispatch({
|
|
234
|
+
type: "available",
|
|
235
|
+
coValue: { id: mockCoValueId } as CoValueCore,
|
|
236
|
+
});
|
|
237
|
+
}, 100);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const mockPeers = [peer1] as unknown as PeerState[];
|
|
243
|
+
|
|
244
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
245
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
246
|
+
|
|
247
|
+
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
|
|
248
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES + 1; i++) {
|
|
249
|
+
await vi.runAllTimersAsync();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await loadPromise;
|
|
253
|
+
|
|
254
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(2);
|
|
255
|
+
expect(state.state.type).toBe("available");
|
|
256
|
+
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
|
|
257
|
+
vi.useRealTimers();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("should have a coValue as value property when becomes available after that have been marked as unavailable", async () => {
|
|
261
|
+
vi.useFakeTimers();
|
|
262
|
+
|
|
263
|
+
const peer1 = createMockPeerState(
|
|
264
|
+
{
|
|
265
|
+
id: "peer1",
|
|
266
|
+
role: "server",
|
|
267
|
+
},
|
|
268
|
+
async () => {
|
|
269
|
+
state.dispatch({
|
|
270
|
+
type: "not-found-in-peer",
|
|
271
|
+
peerId: "peer1",
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const mockPeers = [peer1] as unknown as PeerState[];
|
|
277
|
+
|
|
278
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
279
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
280
|
+
|
|
281
|
+
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
|
|
282
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
|
|
283
|
+
await vi.runAllTimersAsync();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
state.dispatch({
|
|
287
|
+
type: "available",
|
|
288
|
+
coValue: { id: mockCoValueId } as CoValueCore,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await loadPromise;
|
|
292
|
+
|
|
293
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(5);
|
|
294
|
+
expect(state.state.type).toBe("available");
|
|
295
|
+
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
|
|
296
|
+
|
|
297
|
+
vi.useRealTimers();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("should stop retrying when value becomes available", async () => {
|
|
301
|
+
vi.useFakeTimers();
|
|
302
|
+
|
|
303
|
+
let run = 1;
|
|
304
|
+
|
|
305
|
+
const peer1 = createMockPeerState(
|
|
306
|
+
{
|
|
307
|
+
id: "peer1",
|
|
308
|
+
role: "server",
|
|
309
|
+
},
|
|
310
|
+
async () => {
|
|
311
|
+
if (run > 2) {
|
|
312
|
+
state.dispatch({
|
|
313
|
+
type: "available",
|
|
314
|
+
coValue: { id: mockCoValueId } as CoValueCore,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
state.dispatch({
|
|
318
|
+
type: "not-found-in-peer",
|
|
319
|
+
peerId: "peer1",
|
|
320
|
+
});
|
|
321
|
+
run++;
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const mockPeers = [peer1] as unknown as PeerState[];
|
|
326
|
+
|
|
327
|
+
const state = CoValueState.Unknown(mockCoValueId);
|
|
328
|
+
const loadPromise = state.loadFromPeers(mockPeers);
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
|
|
331
|
+
await vi.runAllTimersAsync();
|
|
332
|
+
}
|
|
333
|
+
await loadPromise;
|
|
334
|
+
|
|
335
|
+
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(3);
|
|
336
|
+
expect(state.state.type).toBe("available");
|
|
337
|
+
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
|
|
338
|
+
|
|
339
|
+
vi.useRealTimers();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
function createMockPeerState(
|
|
344
|
+
peer: Partial<Peer>,
|
|
345
|
+
pushFn = () => Promise.resolve(),
|
|
346
|
+
) {
|
|
347
|
+
const peerState = new PeerState(
|
|
348
|
+
{
|
|
349
|
+
id: "peer",
|
|
350
|
+
role: "server",
|
|
351
|
+
outgoing: {
|
|
352
|
+
push: pushFn,
|
|
353
|
+
},
|
|
354
|
+
...peer,
|
|
355
|
+
} as Peer,
|
|
356
|
+
undefined,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
vi.spyOn(peerState, "pushOutgoingMessage").mockImplementation(pushFn);
|
|
360
|
+
|
|
361
|
+
return peerState;
|
|
362
|
+
}
|
package/src/tests/group.test.ts
CHANGED
|
@@ -42,7 +42,7 @@ test("Can create a CoStream in a group", () => {
|
|
|
42
42
|
expect(stream instanceof RawCoStream).toEqual(true);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
test("Can create a
|
|
45
|
+
test("Can create a FileStream in a group", () => {
|
|
46
46
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
47
47
|
|
|
48
48
|
const group = node.createGroup();
|
package/src/tests/sync.test.ts
CHANGED
|
@@ -807,7 +807,7 @@ test.skip("When replaying creation and transactions of a coValue as new content,
|
|
|
807
807
|
sessions: {},
|
|
808
808
|
} satisfies SyncMessage);
|
|
809
809
|
|
|
810
|
-
expect(node2.
|
|
810
|
+
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("loading");
|
|
811
811
|
|
|
812
812
|
await inTx2.push(mapNewContentMsg);
|
|
813
813
|
|
|
@@ -875,7 +875,7 @@ test("Can sync a coValue through a server to another client", async () => {
|
|
|
875
875
|
{
|
|
876
876
|
peer1role: "server",
|
|
877
877
|
peer2role: "client",
|
|
878
|
-
trace: true,
|
|
878
|
+
// trace: true,
|
|
879
879
|
},
|
|
880
880
|
);
|
|
881
881
|
|
|
@@ -894,7 +894,7 @@ test("Can sync a coValue through a server to another client", async () => {
|
|
|
894
894
|
{
|
|
895
895
|
peer1role: "server",
|
|
896
896
|
peer2role: "client",
|
|
897
|
-
trace: true,
|
|
897
|
+
// trace: true,
|
|
898
898
|
},
|
|
899
899
|
);
|
|
900
900
|
|
|
@@ -926,7 +926,7 @@ test("Can sync a coValue with private transactions through a server to another c
|
|
|
926
926
|
const server = new LocalNode(serverUser, serverSession, Crypto);
|
|
927
927
|
|
|
928
928
|
const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", {
|
|
929
|
-
trace: true,
|
|
929
|
+
// trace: true,
|
|
930
930
|
peer1role: "server",
|
|
931
931
|
peer2role: "client",
|
|
932
932
|
});
|
|
@@ -944,7 +944,7 @@ test("Can sync a coValue with private transactions through a server to another c
|
|
|
944
944
|
"server",
|
|
945
945
|
"client2",
|
|
946
946
|
{
|
|
947
|
-
trace: true,
|
|
947
|
+
// trace: true,
|
|
948
948
|
peer1role: "server",
|
|
949
949
|
peer2role: "client",
|
|
950
950
|
},
|
|
@@ -1095,14 +1095,14 @@ test("If we start loading a coValue before connecting to a peer that has it, it
|
|
|
1095
1095
|
const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2", {
|
|
1096
1096
|
peer1role: "server",
|
|
1097
1097
|
peer2role: "client",
|
|
1098
|
-
trace: true,
|
|
1098
|
+
// trace: true,
|
|
1099
1099
|
});
|
|
1100
1100
|
|
|
1101
1101
|
node1.syncManager.addPeer(node2asPeer);
|
|
1102
1102
|
|
|
1103
1103
|
const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
|
|
1104
1104
|
|
|
1105
|
-
expect(node2.
|
|
1105
|
+
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("unknown");
|
|
1106
1106
|
|
|
1107
1107
|
node2.syncManager.addPeer(node1asPeer);
|
|
1108
1108
|
|
|
@@ -1116,6 +1116,59 @@ test("If we start loading a coValue before connecting to a peer that has it, it
|
|
|
1116
1116
|
);
|
|
1117
1117
|
});
|
|
1118
1118
|
|
|
1119
|
+
test("should keep the peer state when the peer closes", async () => {
|
|
1120
|
+
const {
|
|
1121
|
+
client,
|
|
1122
|
+
jazzCloud,
|
|
1123
|
+
jazzCloudConnectionAsPeer,
|
|
1124
|
+
connectionWithClientAsPeer,
|
|
1125
|
+
} = createTwoConnectedNodes();
|
|
1126
|
+
|
|
1127
|
+
const group = jazzCloud.createGroup();
|
|
1128
|
+
const map = group.createMap();
|
|
1129
|
+
map.set("hello", "world", "trusting");
|
|
1130
|
+
|
|
1131
|
+
await client.loadCoValueCore(map.core.id);
|
|
1132
|
+
|
|
1133
|
+
const syncManager = client.syncManager;
|
|
1134
|
+
const peerState = syncManager.peers[jazzCloudConnectionAsPeer.id];
|
|
1135
|
+
|
|
1136
|
+
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
1137
|
+
await connectionWithClientAsPeer.outgoing.push("Disconnected");
|
|
1138
|
+
|
|
1139
|
+
await waitFor(() => peerState?.closed);
|
|
1140
|
+
|
|
1141
|
+
expect(syncManager.peers[jazzCloudConnectionAsPeer.id]).not.toBeUndefined();
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
test("should delete the peer state when the peer closes if deletePeerStateOnClose is true", async () => {
|
|
1145
|
+
const {
|
|
1146
|
+
client,
|
|
1147
|
+
jazzCloud,
|
|
1148
|
+
jazzCloudConnectionAsPeer,
|
|
1149
|
+
connectionWithClientAsPeer,
|
|
1150
|
+
} = createTwoConnectedNodes();
|
|
1151
|
+
|
|
1152
|
+
jazzCloudConnectionAsPeer.deletePeerStateOnClose = true;
|
|
1153
|
+
|
|
1154
|
+
const group = jazzCloud.createGroup();
|
|
1155
|
+
const map = group.createMap();
|
|
1156
|
+
map.set("hello", "world", "trusting");
|
|
1157
|
+
|
|
1158
|
+
await client.loadCoValueCore(map.core.id);
|
|
1159
|
+
|
|
1160
|
+
const syncManager = client.syncManager;
|
|
1161
|
+
|
|
1162
|
+
const peerState = syncManager.peers[jazzCloudConnectionAsPeer.id];
|
|
1163
|
+
|
|
1164
|
+
// @ts-expect-error Simulating a peer closing, leveraging the direct connection between the client/server peers
|
|
1165
|
+
await connectionWithClientAsPeer.outgoing.push("Disconnected");
|
|
1166
|
+
|
|
1167
|
+
await waitFor(() => peerState?.closed);
|
|
1168
|
+
|
|
1169
|
+
expect(syncManager.peers[jazzCloudConnectionAsPeer.id]).toBeUndefined();
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1119
1172
|
describe("sync - extra tests", () => {
|
|
1120
1173
|
test("Node handles disconnection and reconnection of a peer gracefully", async () => {
|
|
1121
1174
|
// Create two nodes
|
|
@@ -1519,7 +1572,7 @@ describe("sync - extra tests", () => {
|
|
|
1519
1572
|
{
|
|
1520
1573
|
peer1role: "server",
|
|
1521
1574
|
peer2role: "client",
|
|
1522
|
-
trace: true,
|
|
1575
|
+
// trace: true,
|
|
1523
1576
|
},
|
|
1524
1577
|
);
|
|
1525
1578
|
|
|
@@ -1529,7 +1582,7 @@ describe("sync - extra tests", () => {
|
|
|
1529
1582
|
{
|
|
1530
1583
|
peer1role: "server",
|
|
1531
1584
|
peer2role: "client",
|
|
1532
|
-
trace: true,
|
|
1585
|
+
// trace: true,
|
|
1533
1586
|
},
|
|
1534
1587
|
);
|
|
1535
1588
|
|
|
@@ -1590,32 +1643,39 @@ function createTwoConnectedNodes() {
|
|
|
1590
1643
|
|
|
1591
1644
|
describe("SyncManager - knownStates vs optimisticKnownStates", () => {
|
|
1592
1645
|
test("knownStates and optimisticKnownStates are the same when the coValue is fully synced", async () => {
|
|
1593
|
-
const { client } = createTwoConnectedNodes();
|
|
1646
|
+
const { client, jazzCloud } = createTwoConnectedNodes();
|
|
1594
1647
|
|
|
1595
1648
|
// Create test data
|
|
1596
1649
|
const group = client.createGroup();
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1650
|
+
const mapOnClient = group.createMap();
|
|
1651
|
+
mapOnClient.set("key1", "value1", "trusting");
|
|
1599
1652
|
|
|
1600
|
-
await client.syncManager.actuallySyncCoValue(
|
|
1653
|
+
await client.syncManager.actuallySyncCoValue(mapOnClient.core);
|
|
1601
1654
|
|
|
1602
1655
|
// Wait for the full sync to complete
|
|
1603
1656
|
await waitFor(() => {
|
|
1604
1657
|
return client.syncManager.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
|
|
1605
1658
|
"jazzCloudConnection",
|
|
1606
|
-
|
|
1659
|
+
mapOnClient.core.id,
|
|
1607
1660
|
);
|
|
1608
1661
|
});
|
|
1609
1662
|
|
|
1610
|
-
const
|
|
1663
|
+
const peerStateClient = client.syncManager.peers["jazzCloudConnection"]!;
|
|
1664
|
+
const peerStateJazzCloud =
|
|
1665
|
+
jazzCloud.syncManager.peers["connectionWithClient"]!;
|
|
1611
1666
|
|
|
1612
1667
|
// The optimisticKnownStates should be the same as the knownStates after the full sync is complete
|
|
1613
|
-
expect(
|
|
1614
|
-
|
|
1615
|
-
);
|
|
1668
|
+
expect(
|
|
1669
|
+
peerStateClient.optimisticKnownStates.get(mapOnClient.core.id),
|
|
1670
|
+
).toEqual(peerStateClient.knownStates.get(mapOnClient.core.id));
|
|
1671
|
+
|
|
1672
|
+
// On the other node the knownStates should be updated correctly based on the messages we received
|
|
1673
|
+
expect(
|
|
1674
|
+
peerStateJazzCloud.optimisticKnownStates.get(mapOnClient.core.id),
|
|
1675
|
+
).toEqual(peerStateJazzCloud.knownStates.get(mapOnClient.core.id));
|
|
1616
1676
|
});
|
|
1617
1677
|
|
|
1618
|
-
test("optimisticKnownStates is updated as new transactions are
|
|
1678
|
+
test("optimisticKnownStates is updated as new transactions are sent, while knownStates only when the updates are acknowledged", async () => {
|
|
1619
1679
|
const { client, jazzCloudConnectionAsPeer } = createTwoConnectedNodes();
|
|
1620
1680
|
|
|
1621
1681
|
// Create test data and sync the first change
|
|
@@ -1814,6 +1874,63 @@ describe("SyncManager.addPeer", () => {
|
|
|
1814
1874
|
});
|
|
1815
1875
|
});
|
|
1816
1876
|
|
|
1877
|
+
describe("loadCoValueCore with retry", () => {
|
|
1878
|
+
test("should load the value if available on the server", async () => {
|
|
1879
|
+
const { client, jazzCloud } = createTwoConnectedNodes();
|
|
1880
|
+
|
|
1881
|
+
const anotherClient = createTestNode();
|
|
1882
|
+
const [
|
|
1883
|
+
connectionWithAnotherClientAsPeer,
|
|
1884
|
+
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1885
|
+
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
|
|
1886
|
+
peer1role: "client",
|
|
1887
|
+
peer2role: "server",
|
|
1888
|
+
});
|
|
1889
|
+
|
|
1890
|
+
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
|
|
1891
|
+
|
|
1892
|
+
const group = anotherClient.createGroup();
|
|
1893
|
+
const map = group.createMap();
|
|
1894
|
+
map.set("key1", "value1", "trusting");
|
|
1895
|
+
|
|
1896
|
+
const promise = client.loadCoValueCore(map.id);
|
|
1897
|
+
|
|
1898
|
+
anotherClient.syncManager.addPeer(
|
|
1899
|
+
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1900
|
+
);
|
|
1901
|
+
await expect(promise).resolves.not.toBe("unavailable");
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
test("should handle correctly two subsequent loads", async () => {
|
|
1905
|
+
const { client, jazzCloud } = createTwoConnectedNodes();
|
|
1906
|
+
|
|
1907
|
+
const anotherClient = createTestNode();
|
|
1908
|
+
const [
|
|
1909
|
+
connectionWithAnotherClientAsPeer,
|
|
1910
|
+
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1911
|
+
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
|
|
1912
|
+
peer1role: "client",
|
|
1913
|
+
peer2role: "server",
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
|
|
1917
|
+
|
|
1918
|
+
const group = anotherClient.createGroup();
|
|
1919
|
+
const map = group.createMap();
|
|
1920
|
+
map.set("key1", "value1", "trusting");
|
|
1921
|
+
|
|
1922
|
+
const promise1 = client.loadCoValueCore(map.id);
|
|
1923
|
+
const promise2 = client.loadCoValueCore(map.id);
|
|
1924
|
+
|
|
1925
|
+
anotherClient.syncManager.addPeer(
|
|
1926
|
+
jazzCloudConnectionAsPeerForAnotherClient,
|
|
1927
|
+
);
|
|
1928
|
+
|
|
1929
|
+
await expect(promise1).resolves.not.toBe("unavailable");
|
|
1930
|
+
await expect(promise2).resolves.not.toBe("unavailable");
|
|
1931
|
+
});
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1817
1934
|
describe("waitForUploadIntoPeer", () => {
|
|
1818
1935
|
test("should resolve when the coValue is fully uploaded into the peer", async () => {
|
|
1819
1936
|
const { client, jazzCloudConnectionAsPeer: peer } =
|
|
@@ -1889,3 +2006,7 @@ function _admStateEx(adminID: RawAccountID) {
|
|
|
1889
2006
|
id: adminID,
|
|
1890
2007
|
};
|
|
1891
2008
|
}
|
|
2009
|
+
|
|
2010
|
+
function sleep(ms: number) {
|
|
2011
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2012
|
+
}
|