cojson 0.13.10 → 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 +9 -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/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/sync.mesh.test.js +65 -3
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerKnownStates.ts +19 -56
- package/src/PeerState.ts +70 -12
- 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/sync.mesh.test.ts +81 -3
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
|
+
});
|
|
@@ -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();
|