cojson 0.13.7 → 0.13.11
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 +15 -0
- package/dist/PeerKnownStates.d.ts +5 -25
- package/dist/PeerKnownStates.d.ts.map +1 -1
- package/dist/PeerKnownStates.js +7 -20
- package/dist/PeerKnownStates.js.map +1 -1
- package/dist/PeerState.d.ts +14 -7
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +51 -7
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore.d.ts +3 -1
- package/dist/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore.js +25 -8
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coList.d.ts +13 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +60 -34
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +45 -0
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +61 -11
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +8 -51
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerKnownStates.test.js +9 -14
- package/dist/tests/PeerKnownStates.test.js.map +1 -1
- package/dist/tests/PeerState.test.js +22 -34
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/coList.test.js +63 -0
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +66 -11
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +15 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +65 -3
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +36 -0
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +3 -2
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerKnownStates.ts +19 -56
- package/src/PeerState.ts +70 -12
- package/src/coValueCore.ts +36 -7
- package/src/coValues/coList.ts +84 -44
- package/src/coValues/coPlainText.ts +75 -11
- package/src/sync.ts +11 -52
- package/src/tests/PeerKnownStates.test.ts +9 -14
- package/src/tests/PeerState.test.ts +27 -40
- package/src/tests/coList.test.ts +83 -0
- package/src/tests/coPlainText.test.ts +81 -11
- package/src/tests/coValueCore.test.ts +20 -2
- package/src/tests/sync.mesh.test.ts +81 -3
- package/src/tests/sync.peerReconciliation.test.ts +50 -0
- package/src/tests/testUtils.ts +4 -2
package/src/tests/coList.test.ts
CHANGED
|
@@ -95,6 +95,76 @@ test("appendItems add an array of items at the end of the list", () => {
|
|
|
95
95
|
expect(content.toJSON()).toEqual(["hello", "world", "hooray", "universe"]);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test("appendItems at index", () => {
|
|
99
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
100
|
+
|
|
101
|
+
const coValue = node.createCoValue({
|
|
102
|
+
type: "colist",
|
|
103
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
104
|
+
meta: null,
|
|
105
|
+
...Crypto.createdNowUnique(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const content = expectList(coValue.getCurrentContent());
|
|
109
|
+
|
|
110
|
+
content.append("first", 0, "trusting");
|
|
111
|
+
content.append("second", 0, "trusting");
|
|
112
|
+
expect(content.toJSON()).toEqual(["first", "second"]);
|
|
113
|
+
|
|
114
|
+
content.appendItems(["third", "fourth"], 1, "trusting");
|
|
115
|
+
expect(content.toJSON()).toEqual(["first", "second", "third", "fourth"]);
|
|
116
|
+
|
|
117
|
+
content.appendItems(["hello", "world"], 0, "trusting");
|
|
118
|
+
expect(content.toJSON()).toEqual([
|
|
119
|
+
"first",
|
|
120
|
+
"hello",
|
|
121
|
+
"world",
|
|
122
|
+
"second",
|
|
123
|
+
"third",
|
|
124
|
+
"fourth",
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("appendItems at index", () => {
|
|
129
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
130
|
+
|
|
131
|
+
const coValue = node.createCoValue({
|
|
132
|
+
type: "colist",
|
|
133
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
134
|
+
meta: null,
|
|
135
|
+
...Crypto.createdNowUnique(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const content = expectList(coValue.getCurrentContent());
|
|
139
|
+
|
|
140
|
+
content.append("first", 0, "trusting");
|
|
141
|
+
expect(content.toJSON()).toEqual(["first"]);
|
|
142
|
+
|
|
143
|
+
content.appendItems(["second"], 0, "trusting");
|
|
144
|
+
expect(content.toJSON()).toEqual(["first", "second"]);
|
|
145
|
+
|
|
146
|
+
content.appendItems(["third"], 1, "trusting");
|
|
147
|
+
expect(content.toJSON()).toEqual(["first", "second", "third"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("appendItems with negative index", () => {
|
|
151
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
152
|
+
|
|
153
|
+
const coValue = node.createCoValue({
|
|
154
|
+
type: "colist",
|
|
155
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
156
|
+
meta: null,
|
|
157
|
+
...Crypto.createdNowUnique(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const content = expectList(coValue.getCurrentContent());
|
|
161
|
+
|
|
162
|
+
content.append("hello", 0, "trusting");
|
|
163
|
+
expect(content.toJSON()).toEqual(["hello"]);
|
|
164
|
+
content.appendItems(["world", "hooray", "universe"], -1, "trusting");
|
|
165
|
+
expect(content.toJSON()).toEqual(["hello", "world", "hooray", "universe"]);
|
|
166
|
+
});
|
|
167
|
+
|
|
98
168
|
test("Can push into empty list", () => {
|
|
99
169
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
100
170
|
|
|
@@ -151,3 +221,16 @@ test("Items prepended to start appear with latest first", () => {
|
|
|
151
221
|
|
|
152
222
|
expect(content.toJSON()).toEqual(["third", "second", "first"]);
|
|
153
223
|
});
|
|
224
|
+
|
|
225
|
+
test("should handle large lists", () => {
|
|
226
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
227
|
+
|
|
228
|
+
const group = node.createGroup();
|
|
229
|
+
const coValue = group.createList();
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < 8_000; i++) {
|
|
232
|
+
coValue.append(`item ${i}`, undefined, "trusting");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
expect(coValue.toJSON().length).toEqual(8_000);
|
|
236
|
+
});
|
|
@@ -71,25 +71,23 @@ test("Can insert and delete in CoPlainText", () => {
|
|
|
71
71
|
content.insertAfter(0, "hello", "trusting");
|
|
72
72
|
expect(content.toString()).toEqual("hello");
|
|
73
73
|
|
|
74
|
-
content.insertAfter(
|
|
74
|
+
content.insertAfter(4, " world", "trusting");
|
|
75
75
|
expect(content.toString()).toEqual("hello world");
|
|
76
76
|
|
|
77
|
-
content.
|
|
77
|
+
content.insertBefore(0, "Hello, ", "trusting");
|
|
78
78
|
expect(content.toString()).toEqual("Hello, hello world");
|
|
79
79
|
|
|
80
|
-
console.log("first delete");
|
|
81
80
|
content.deleteRange({ from: 6, to: 12 }, "trusting");
|
|
82
81
|
expect(content.toString()).toEqual("Hello, world");
|
|
83
82
|
|
|
84
|
-
content.
|
|
83
|
+
content.insertBefore(2, "😍", "trusting");
|
|
85
84
|
expect(content.toString()).toEqual("He😍llo, world");
|
|
86
85
|
|
|
87
|
-
console.log("second delete");
|
|
88
86
|
content.deleteRange({ from: 2, to: 4 }, "trusting");
|
|
89
87
|
expect(content.toString()).toEqual("Hello, world");
|
|
90
88
|
});
|
|
91
89
|
|
|
92
|
-
test("Multiple items
|
|
90
|
+
test("Multiple items inserted appear in correct order", () => {
|
|
93
91
|
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
94
92
|
|
|
95
93
|
const coValue = node.createCoValue({
|
|
@@ -101,10 +99,10 @@ test("Multiple items appended after start appear in correct order", () => {
|
|
|
101
99
|
|
|
102
100
|
const content = expectPlainText(coValue.getCurrentContent());
|
|
103
101
|
|
|
104
|
-
// Add multiple items in
|
|
102
|
+
// Add multiple items in sequence
|
|
105
103
|
content.insertAfter(0, "h", "trusting");
|
|
106
|
-
content.insertAfter(
|
|
107
|
-
content.insertAfter(
|
|
104
|
+
content.insertAfter(0, "e", "trusting");
|
|
105
|
+
content.insertAfter(1, "y", "trusting");
|
|
108
106
|
|
|
109
107
|
// They should appear in insertion order (hey), not reversed (yeh)
|
|
110
108
|
expect(content.toString()).toEqual("hey");
|
|
@@ -124,10 +122,82 @@ test("Items inserted at start appear with latest first", () => {
|
|
|
124
122
|
|
|
125
123
|
// Insert multiple items at the start
|
|
126
124
|
content.insertAfter(0, "first", "trusting");
|
|
127
|
-
content.
|
|
128
|
-
content.
|
|
125
|
+
content.insertBefore(0, "second", "trusting");
|
|
126
|
+
content.insertBefore(0, "third", "trusting");
|
|
129
127
|
|
|
130
128
|
// They should appear in reverse chronological order
|
|
131
129
|
// because newer items should appear before older items
|
|
132
130
|
expect(content.toString()).toEqual("thirdsecondfirst");
|
|
133
131
|
});
|
|
132
|
+
|
|
133
|
+
test("Handles different locales correctly", () => {
|
|
134
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
135
|
+
|
|
136
|
+
// Test with explicit locale in meta
|
|
137
|
+
const coValueJa = node.createCoValue({
|
|
138
|
+
type: "coplaintext",
|
|
139
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
140
|
+
meta: { locale: "ja-JP" },
|
|
141
|
+
...Crypto.createdNowUnique(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const contentJa = expectPlainText(coValueJa.getCurrentContent());
|
|
145
|
+
contentJa.insertAfter(0, "こんにちは", "trusting");
|
|
146
|
+
expect(contentJa.toString()).toEqual("こんにちは");
|
|
147
|
+
|
|
148
|
+
// Test browser locale fallback
|
|
149
|
+
vi.stubGlobal("navigator", { language: "fr-FR" });
|
|
150
|
+
|
|
151
|
+
const coValueBrowser = node.createCoValue({
|
|
152
|
+
type: "coplaintext",
|
|
153
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
154
|
+
meta: null,
|
|
155
|
+
...Crypto.createdNowUnique(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const contentBrowser = expectPlainText(coValueBrowser.getCurrentContent());
|
|
159
|
+
contentBrowser.insertAfter(0, "bonjour", "trusting");
|
|
160
|
+
expect(contentBrowser.toString()).toEqual("bonjour");
|
|
161
|
+
|
|
162
|
+
// Test fallback to 'en' when no navigator
|
|
163
|
+
vi.stubGlobal("navigator", undefined);
|
|
164
|
+
|
|
165
|
+
const coValueFallback = node.createCoValue({
|
|
166
|
+
type: "coplaintext",
|
|
167
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
168
|
+
meta: null,
|
|
169
|
+
...Crypto.createdNowUnique(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const contentFallback = expectPlainText(coValueFallback.getCurrentContent());
|
|
173
|
+
contentFallback.insertAfter(0, "hello", "trusting");
|
|
174
|
+
expect(contentFallback.toString()).toEqual("hello");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("insertBefore and insertAfter work as expected", () => {
|
|
178
|
+
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
|
179
|
+
const coValue = node.createCoValue({
|
|
180
|
+
type: "coplaintext",
|
|
181
|
+
ruleset: { type: "unsafeAllowAll" },
|
|
182
|
+
meta: null,
|
|
183
|
+
...Crypto.createdNowUnique(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const content = expectPlainText(coValue.getCurrentContent());
|
|
187
|
+
|
|
188
|
+
// Insert 'h' at start
|
|
189
|
+
content.insertBefore(0, "h", "trusting"); // "h"
|
|
190
|
+
expect(content.toString()).toEqual("h");
|
|
191
|
+
|
|
192
|
+
// Insert 'e' after 'h'
|
|
193
|
+
content.insertAfter(0, "e", "trusting"); // "he"
|
|
194
|
+
expect(content.toString()).toEqual("he");
|
|
195
|
+
|
|
196
|
+
// Insert 'y' after 'e'
|
|
197
|
+
content.insertAfter(1, "y", "trusting"); // "hey"
|
|
198
|
+
expect(content.toString()).toEqual("hey");
|
|
199
|
+
|
|
200
|
+
// Insert '!' at start
|
|
201
|
+
content.insertBefore(0, "!", "trusting"); // "!hey"
|
|
202
|
+
expect(content.toString()).toEqual("!hey");
|
|
203
|
+
});
|
|
@@ -287,12 +287,30 @@ test("creating a coValue with a group should't trigger automatically a content c
|
|
|
287
287
|
});
|
|
288
288
|
|
|
289
289
|
// It's called once for the group and never for the coValue
|
|
290
|
-
expect(getCurrentContentSpy).toHaveBeenCalledTimes(
|
|
291
|
-
expect(groupSpy).toHaveBeenCalledTimes(
|
|
290
|
+
expect(getCurrentContentSpy).toHaveBeenCalledTimes(0);
|
|
291
|
+
expect(groupSpy).toHaveBeenCalledTimes(0);
|
|
292
292
|
|
|
293
293
|
getCurrentContentSpy.mockRestore();
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
test("loading a coValue core without having the owner group available doesn't crash", () => {
|
|
297
|
+
const [account, sessionID] = randomAnonymousAccountAndSessionID();
|
|
298
|
+
const node = new LocalNode(account, sessionID, Crypto);
|
|
299
|
+
|
|
300
|
+
const otherNode = createTestNode();
|
|
301
|
+
|
|
302
|
+
const group = otherNode.createGroup();
|
|
303
|
+
|
|
304
|
+
const coValue = node.createCoValue({
|
|
305
|
+
type: "costream",
|
|
306
|
+
ruleset: { type: "ownedByGroup", group: group.id },
|
|
307
|
+
meta: null,
|
|
308
|
+
...Crypto.createdNowUnique(),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(coValue.id).toBeDefined();
|
|
312
|
+
});
|
|
313
|
+
|
|
296
314
|
test("listeners are notified even if the previous listener threw an error", async () => {
|
|
297
315
|
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
298
316
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
2
|
|
|
3
|
+
import { expectMap } from "../coValue";
|
|
3
4
|
import {
|
|
4
5
|
SyncMessagesLog,
|
|
5
6
|
loadCoValueOrFail,
|
|
@@ -8,9 +9,7 @@ import {
|
|
|
8
9
|
} from "./testUtils";
|
|
9
10
|
|
|
10
11
|
function setupMesh() {
|
|
11
|
-
const coreServer = setupTestNode(
|
|
12
|
-
isSyncServer: true,
|
|
13
|
-
});
|
|
12
|
+
const coreServer = setupTestNode();
|
|
14
13
|
|
|
15
14
|
coreServer.addStoragePeer({
|
|
16
15
|
ourName: "core",
|
|
@@ -20,12 +19,14 @@ function setupMesh() {
|
|
|
20
19
|
edgeItaly.connectToSyncServer({
|
|
21
20
|
ourName: "edge-italy",
|
|
22
21
|
syncServerName: "core",
|
|
22
|
+
syncServer: coreServer.node,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
const edgeFrance = setupTestNode();
|
|
26
26
|
edgeFrance.connectToSyncServer({
|
|
27
27
|
ourName: "edge-france",
|
|
28
28
|
syncServerName: "core",
|
|
29
|
+
syncServer: coreServer.node,
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
return { coreServer, edgeItaly, edgeFrance };
|
|
@@ -161,6 +162,83 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
|
|
|
161
162
|
`);
|
|
162
163
|
});
|
|
163
164
|
|
|
165
|
+
test("syncs corrections from multiple peers", async () => {
|
|
166
|
+
const client = setupTestNode();
|
|
167
|
+
|
|
168
|
+
client.connectToSyncServer({
|
|
169
|
+
syncServerName: "edge-italy",
|
|
170
|
+
syncServer: mesh.edgeItaly.node,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const group = mesh.edgeItaly.node.createGroup();
|
|
174
|
+
group.addMember("everyone", "writer");
|
|
175
|
+
|
|
176
|
+
const map = group.createMap({
|
|
177
|
+
fromServer: "initial",
|
|
178
|
+
fromClient: "initial",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Load the coValue on the client
|
|
182
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
183
|
+
const mapOnCoreServer = await loadCoValueOrFail(
|
|
184
|
+
mesh.coreServer.node,
|
|
185
|
+
map.id,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Forcefully delete the coValue from the edge (simulating some data loss)
|
|
189
|
+
mesh.edgeItaly.node.coValuesStore.coValues.delete(map.id);
|
|
190
|
+
|
|
191
|
+
mapOnClient.set("fromClient", "updated", "trusting");
|
|
192
|
+
mapOnCoreServer.set("fromServer", "updated", "trusting");
|
|
193
|
+
|
|
194
|
+
await waitFor(() => {
|
|
195
|
+
const coValue = expectMap(
|
|
196
|
+
mesh.edgeItaly.node.expectCoValueLoaded(map.id).getCurrentContent(),
|
|
197
|
+
);
|
|
198
|
+
expect(coValue.get("fromServer")).toEqual("updated");
|
|
199
|
+
expect(coValue.get("fromClient")).toEqual("updated");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(
|
|
203
|
+
SyncMessagesLog.getMessages({
|
|
204
|
+
Group: group.core,
|
|
205
|
+
Map: map.core,
|
|
206
|
+
}),
|
|
207
|
+
).toMatchInlineSnapshot(`
|
|
208
|
+
[
|
|
209
|
+
"client -> edge-italy | LOAD Map sessions: empty",
|
|
210
|
+
"edge-italy -> core | CONTENT Group header: true new: After: 0 New: 5",
|
|
211
|
+
"edge-italy -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
212
|
+
"core -> edge-italy | KNOWN Group sessions: header/5",
|
|
213
|
+
"client -> edge-italy | KNOWN Group sessions: header/5",
|
|
214
|
+
"core -> storage | CONTENT Group header: true new: After: 0 New: 5",
|
|
215
|
+
"edge-italy -> core | CONTENT Map header: true new: After: 0 New: 1",
|
|
216
|
+
"edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
217
|
+
"storage -> core | KNOWN Group sessions: header/5",
|
|
218
|
+
"core -> edge-italy | KNOWN Map sessions: header/1",
|
|
219
|
+
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
220
|
+
"client -> edge-italy | KNOWN Map sessions: header/1",
|
|
221
|
+
"storage -> core | KNOWN Map sessions: header/1",
|
|
222
|
+
"client -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
|
|
223
|
+
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
224
|
+
"edge-italy -> client | KNOWN CORRECTION Map sessions: empty",
|
|
225
|
+
"storage -> core | KNOWN Map sessions: header/2",
|
|
226
|
+
"core -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
|
|
227
|
+
"client -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
|
228
|
+
"edge-italy -> core | KNOWN CORRECTION Map sessions: empty",
|
|
229
|
+
"edge-italy -> client | KNOWN Map sessions: header/2",
|
|
230
|
+
"core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
|
231
|
+
"edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
|
|
232
|
+
"edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
|
|
233
|
+
"core -> edge-italy | KNOWN Map sessions: header/3",
|
|
234
|
+
"edge-italy -> core | KNOWN Map sessions: header/3",
|
|
235
|
+
"client -> edge-italy | KNOWN Map sessions: header/3",
|
|
236
|
+
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
237
|
+
"storage -> core | KNOWN Map sessions: header/3",
|
|
238
|
+
]
|
|
239
|
+
`);
|
|
240
|
+
});
|
|
241
|
+
|
|
164
242
|
test("sync of changes of a coValue with bad signatures should be blocked", async () => {
|
|
165
243
|
const italianClient = setupTestNode();
|
|
166
244
|
const frenchClient = setupTestNode();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert, beforeEach, describe, expect, test } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue";
|
|
3
3
|
import { WasmCrypto } from "../crypto/WasmCrypto";
|
|
4
|
+
import { CoValueCore, RawCoMap } from "../exports";
|
|
4
5
|
import { LocalNode } from "../localNode";
|
|
5
6
|
import { toSimplifiedMessages } from "./messagesTestUtils";
|
|
6
7
|
import {
|
|
@@ -156,6 +157,55 @@ describe("peer reconciliation", () => {
|
|
|
156
157
|
`);
|
|
157
158
|
});
|
|
158
159
|
|
|
160
|
+
test("correctly handle server restarts in the middle of a sync", async () => {
|
|
161
|
+
const client = setupTestNode();
|
|
162
|
+
|
|
163
|
+
const group = client.node.createGroup();
|
|
164
|
+
const map = group.createMap();
|
|
165
|
+
|
|
166
|
+
map.set("hello", "world", "trusting");
|
|
167
|
+
|
|
168
|
+
await map.core.waitForSync();
|
|
169
|
+
|
|
170
|
+
jazzCloud.restart();
|
|
171
|
+
SyncMessagesLog.clear();
|
|
172
|
+
client.connectToSyncServer();
|
|
173
|
+
|
|
174
|
+
map.set("hello", "updated", "trusting");
|
|
175
|
+
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
177
|
+
|
|
178
|
+
client.connectToSyncServer();
|
|
179
|
+
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
const mapOnSyncServer = jazzCloud.node.coValuesStore.get(map.id);
|
|
182
|
+
|
|
183
|
+
expect(mapOnSyncServer.state.type).toBe("available");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(
|
|
187
|
+
SyncMessagesLog.getMessages({
|
|
188
|
+
Group: group.core,
|
|
189
|
+
Map: map.core,
|
|
190
|
+
}),
|
|
191
|
+
).toMatchInlineSnapshot(`
|
|
192
|
+
[
|
|
193
|
+
"client -> server | LOAD Group sessions: header/3",
|
|
194
|
+
"server -> client | KNOWN Group sessions: empty",
|
|
195
|
+
"client -> server | LOAD Map sessions: header/2",
|
|
196
|
+
"server -> client | KNOWN Map sessions: empty",
|
|
197
|
+
"client -> server | CONTENT Group header: true new: After: 0 New: 3",
|
|
198
|
+
"server -> client | KNOWN Group sessions: header/3",
|
|
199
|
+
"client -> server | CONTENT Map header: true new: After: 0 New: 2",
|
|
200
|
+
"server -> client | KNOWN Map sessions: header/2",
|
|
201
|
+
"client -> server | LOAD Group sessions: header/3",
|
|
202
|
+
"server -> client | KNOWN Group sessions: header/3",
|
|
203
|
+
"client -> server | LOAD Map sessions: header/2",
|
|
204
|
+
"server -> client | KNOWN Map sessions: header/2",
|
|
205
|
+
]
|
|
206
|
+
`);
|
|
207
|
+
});
|
|
208
|
+
|
|
159
209
|
test.skip("handle peer reconnections with data loss", async () => {
|
|
160
210
|
const client = setupTestNode();
|
|
161
211
|
|
package/src/tests/testUtils.ts
CHANGED
|
@@ -472,13 +472,13 @@ export function setupTestNode(
|
|
|
472
472
|
connectToSyncServer();
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
|
|
475
|
+
const ctx = {
|
|
476
476
|
node,
|
|
477
477
|
connectToSyncServer,
|
|
478
478
|
addStoragePeer,
|
|
479
479
|
restart: () => {
|
|
480
480
|
node.gracefulShutdown();
|
|
481
|
-
node = new LocalNode(admin, session, Crypto);
|
|
481
|
+
ctx.node = node = new LocalNode(admin, session, Crypto);
|
|
482
482
|
|
|
483
483
|
if (opts.isSyncServer) {
|
|
484
484
|
syncServer.current = node;
|
|
@@ -487,6 +487,8 @@ export function setupTestNode(
|
|
|
487
487
|
return node;
|
|
488
488
|
},
|
|
489
489
|
};
|
|
490
|
+
|
|
491
|
+
return ctx;
|
|
490
492
|
}
|
|
491
493
|
|
|
492
494
|
export async function setupTestAccount(
|