cojson 0.13.2 → 0.13.7
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 +16 -0
- package/LICENSE.txt +1 -1
- package/dist/PeerState.d.ts +6 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +43 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore.js +1 -0
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValueState.d.ts +1 -0
- package/dist/coValueState.d.ts.map +1 -1
- package/dist/coValueState.js +27 -2
- package/dist/coValueState.js.map +1 -1
- package/dist/coValues/group.d.ts +1 -0
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +45 -21
- package/dist/coValues/group.js.map +1 -1
- package/dist/crypto/crypto.d.ts +2 -2
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +19 -3
- package/dist/permissions.js.map +1 -1
- package/dist/storage/FileSystem.d.ts +2 -2
- package/dist/storage/FileSystem.d.ts.map +1 -1
- package/dist/sync.d.ts +14 -4
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +146 -146
- package/dist/sync.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +51 -46
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +51 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueState.test.js +31 -4
- package/dist/tests/coValueState.test.js.map +1 -1
- package/dist/tests/group.test.js +135 -2
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +13 -0
- package/dist/tests/messagesTestUtils.d.ts.map +1 -0
- package/dist/tests/messagesTestUtils.js +42 -0
- package/dist/tests/messagesTestUtils.js.map +1 -0
- package/dist/tests/sync.load.test.d.ts +2 -0
- package/dist/tests/sync.load.test.d.ts.map +1 -0
- package/dist/tests/sync.load.test.js +249 -0
- package/dist/tests/sync.load.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.d.ts +2 -0
- package/dist/tests/sync.mesh.test.d.ts.map +1 -0
- package/dist/tests/sync.mesh.test.js +157 -0
- package/dist/tests/sync.mesh.test.js.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts +2 -0
- package/dist/tests/sync.peerReconciliation.test.d.ts.map +1 -0
- package/dist/tests/sync.peerReconciliation.test.js +130 -0
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -0
- package/dist/tests/sync.storage.test.d.ts +2 -0
- package/dist/tests/sync.storage.test.d.ts.map +1 -0
- package/dist/tests/sync.storage.test.js +201 -0
- package/dist/tests/sync.storage.test.js.map +1 -0
- package/dist/tests/sync.test.js +139 -1048
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.d.ts +2 -0
- package/dist/tests/sync.upload.test.d.ts.map +1 -0
- package/dist/tests/sync.upload.test.js +156 -0
- package/dist/tests/sync.upload.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +76 -33
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +153 -47
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +3 -3
- package/src/PeerState.ts +59 -1
- package/src/coValueCore.ts +1 -0
- package/src/coValueState.ts +34 -3
- package/src/coValues/group.ts +83 -45
- package/src/permissions.ts +31 -3
- package/src/sync.ts +169 -185
- package/src/tests/SyncStateManager.test.ts +58 -70
- package/src/tests/coValueCore.test.ts +70 -1
- package/src/tests/coValueState.test.ts +59 -5
- package/src/tests/group.test.ts +250 -2
- package/src/tests/messagesTestUtils.ts +75 -0
- package/src/tests/sync.load.test.ts +327 -0
- package/src/tests/sync.mesh.test.ts +219 -0
- package/src/tests/sync.peerReconciliation.test.ts +201 -0
- package/src/tests/sync.storage.test.ts +259 -0
- package/src/tests/sync.test.ts +170 -1245
- package/src/tests/sync.upload.test.ts +202 -0
- package/src/tests/testUtils.ts +213 -61
package/src/tests/group.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
createTwoConnectedNodes,
|
|
13
13
|
loadCoValueOrFail,
|
|
14
14
|
randomAnonymousAccountAndSessionID,
|
|
15
|
+
waitFor,
|
|
15
16
|
} from "./testUtils.js";
|
|
16
17
|
|
|
17
18
|
const Crypto = await WasmCrypto.create();
|
|
@@ -490,8 +491,6 @@ describe("writeOnly", () => {
|
|
|
490
491
|
"writer",
|
|
491
492
|
);
|
|
492
493
|
|
|
493
|
-
group.core.waitForSync();
|
|
494
|
-
|
|
495
494
|
node2.node.coValuesStore.coValues.delete(map.id);
|
|
496
495
|
expect(node2.node.coValuesStore.get(map.id)).toEqual(
|
|
497
496
|
CoValueState.Unknown(map.id),
|
|
@@ -1127,4 +1126,253 @@ describe("roleOf", () => {
|
|
|
1127
1126
|
// Should fall back to grandparent's everyone role
|
|
1128
1127
|
expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
|
|
1129
1128
|
});
|
|
1129
|
+
|
|
1130
|
+
describe("writeOnly can be used as a role for everyone", () => {
|
|
1131
|
+
test("writeOnly can be used as a role for everyone", async () => {
|
|
1132
|
+
const { node1, node2 } = await createTwoConnectedNodes(
|
|
1133
|
+
"server",
|
|
1134
|
+
"server",
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
const group = node1.node.createGroup();
|
|
1138
|
+
|
|
1139
|
+
group.addMember("everyone", "writeOnly");
|
|
1140
|
+
|
|
1141
|
+
const map = group.createMap();
|
|
1142
|
+
|
|
1143
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1144
|
+
|
|
1145
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1146
|
+
|
|
1147
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1148
|
+
|
|
1149
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1150
|
+
|
|
1151
|
+
await waitFor(async () => {
|
|
1152
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1153
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
test("switching from everyone reader to writeOnly", async () => {
|
|
1158
|
+
const { node1, node2 } = await createTwoConnectedNodes(
|
|
1159
|
+
"server",
|
|
1160
|
+
"server",
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
const group = node1.node.createGroup();
|
|
1164
|
+
|
|
1165
|
+
group.addMember("everyone", "reader");
|
|
1166
|
+
|
|
1167
|
+
group.addMember("everyone", "writeOnly");
|
|
1168
|
+
|
|
1169
|
+
const map = group.createMap();
|
|
1170
|
+
|
|
1171
|
+
map.set("test", "Written from admin");
|
|
1172
|
+
|
|
1173
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1174
|
+
|
|
1175
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1176
|
+
|
|
1177
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1178
|
+
|
|
1179
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1180
|
+
|
|
1181
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1182
|
+
|
|
1183
|
+
await waitFor(async () => {
|
|
1184
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1185
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1186
|
+
});
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test("switching from everyone writer to writeOnly", async () => {
|
|
1190
|
+
const { node1, node2 } = await createTwoConnectedNodes(
|
|
1191
|
+
"server",
|
|
1192
|
+
"server",
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
const group = node1.node.createGroup();
|
|
1196
|
+
|
|
1197
|
+
group.addMember("everyone", "writer");
|
|
1198
|
+
|
|
1199
|
+
group.addMember("everyone", "writeOnly");
|
|
1200
|
+
|
|
1201
|
+
const map = group.createMap();
|
|
1202
|
+
|
|
1203
|
+
map.set("test", "Written from admin");
|
|
1204
|
+
|
|
1205
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1206
|
+
|
|
1207
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1208
|
+
|
|
1209
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1210
|
+
|
|
1211
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1212
|
+
|
|
1213
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1214
|
+
|
|
1215
|
+
await waitFor(async () => {
|
|
1216
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1217
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1218
|
+
});
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
test("switching from everyone writeOnly to reader", async () => {
|
|
1222
|
+
const { node1, node2 } = await createTwoConnectedNodes(
|
|
1223
|
+
"server",
|
|
1224
|
+
"server",
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
const group = node1.node.createGroup();
|
|
1228
|
+
|
|
1229
|
+
group.addMember("everyone", "writeOnly");
|
|
1230
|
+
|
|
1231
|
+
const map = group.createMap();
|
|
1232
|
+
|
|
1233
|
+
map.set("fromAdmin", "Written from admin");
|
|
1234
|
+
|
|
1235
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1236
|
+
|
|
1237
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1238
|
+
|
|
1239
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1240
|
+
|
|
1241
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1242
|
+
|
|
1243
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1244
|
+
|
|
1245
|
+
await waitFor(async () => {
|
|
1246
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1247
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
group.addMember("everyone", "reader");
|
|
1251
|
+
|
|
1252
|
+
await group.core.waitForSync();
|
|
1253
|
+
|
|
1254
|
+
mapOnNode2.set("test", "Updated after the downgrade");
|
|
1255
|
+
|
|
1256
|
+
expect(mapOnNode2.get("test")).toEqual("Written from everyone");
|
|
1257
|
+
expect(mapOnNode2.get("fromAdmin")).toEqual("Written from admin");
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
test("switching from everyone writeOnly to writer", async () => {
|
|
1261
|
+
const { node1, node2 } = await createTwoConnectedNodes(
|
|
1262
|
+
"server",
|
|
1263
|
+
"server",
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
const group = node1.node.createGroup();
|
|
1267
|
+
|
|
1268
|
+
group.addMember("everyone", "writeOnly");
|
|
1269
|
+
|
|
1270
|
+
const map = group.createMap();
|
|
1271
|
+
|
|
1272
|
+
map.set("fromAdmin", "Written from admin");
|
|
1273
|
+
|
|
1274
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1275
|
+
|
|
1276
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1277
|
+
|
|
1278
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1279
|
+
|
|
1280
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1281
|
+
|
|
1282
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1283
|
+
|
|
1284
|
+
await waitFor(async () => {
|
|
1285
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1286
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
group.addMember("everyone", "writer");
|
|
1290
|
+
|
|
1291
|
+
await group.core.waitForSync();
|
|
1292
|
+
|
|
1293
|
+
mapOnNode2.set("test", "Updated after the upgrade");
|
|
1294
|
+
|
|
1295
|
+
expect(mapOnNode2.get("test")).toEqual("Updated after the upgrade");
|
|
1296
|
+
expect(mapOnNode2.get("fromAdmin")).toEqual("Written from admin");
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
test("adding a reader member after writeOnly", async () => {
|
|
1300
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
1301
|
+
"server",
|
|
1302
|
+
"server",
|
|
1303
|
+
"server",
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
const group = node1.node.createGroup();
|
|
1307
|
+
|
|
1308
|
+
group.addMember("everyone", "writeOnly");
|
|
1309
|
+
|
|
1310
|
+
const map = group.createMap();
|
|
1311
|
+
|
|
1312
|
+
map.set("fromAdmin", "Written from admin");
|
|
1313
|
+
|
|
1314
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1315
|
+
|
|
1316
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1317
|
+
|
|
1318
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1319
|
+
|
|
1320
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1321
|
+
|
|
1322
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1323
|
+
|
|
1324
|
+
await waitFor(async () => {
|
|
1325
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1326
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
const account3 = await loadCoValueOrFail(node1.node, node3.accountID);
|
|
1330
|
+
|
|
1331
|
+
group.addMember(account3, "reader");
|
|
1332
|
+
|
|
1333
|
+
await group.core.waitForSync();
|
|
1334
|
+
|
|
1335
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
1336
|
+
|
|
1337
|
+
expect(mapOnNode3.get("test")).toEqual("Written from everyone");
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
test.skip("adding a reader member while creating the writeOnly keys", async () => {
|
|
1341
|
+
const { node1, node2, node3 } = await createThreeConnectedNodes(
|
|
1342
|
+
"server",
|
|
1343
|
+
"server",
|
|
1344
|
+
"server",
|
|
1345
|
+
);
|
|
1346
|
+
const account3 = await loadCoValueOrFail(node1.node, node3.accountID);
|
|
1347
|
+
const group = node1.node.createGroup();
|
|
1348
|
+
|
|
1349
|
+
group.addMember("everyone", "writeOnly");
|
|
1350
|
+
|
|
1351
|
+
const map = group.createMap();
|
|
1352
|
+
|
|
1353
|
+
map.set("fromAdmin", "Written from admin");
|
|
1354
|
+
|
|
1355
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1356
|
+
|
|
1357
|
+
expect(groupOnNode2.myRole()).toEqual("writeOnly");
|
|
1358
|
+
|
|
1359
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
1360
|
+
group.addMember(account3, "reader");
|
|
1361
|
+
|
|
1362
|
+
expect(mapOnNode2.get("test")).toEqual(undefined);
|
|
1363
|
+
|
|
1364
|
+
mapOnNode2.set("test", "Written from everyone");
|
|
1365
|
+
|
|
1366
|
+
await waitFor(async () => {
|
|
1367
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
1368
|
+
expect(mapOnNode1.get("test")).toEqual("Written from everyone");
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
await group.core.waitForSync();
|
|
1372
|
+
|
|
1373
|
+
const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
|
|
1374
|
+
|
|
1375
|
+
expect(mapOnNode3.get("test")).toEqual("Written from everyone");
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1130
1378
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CoValueCore } from "../exports";
|
|
2
|
+
import {
|
|
3
|
+
CoValueKnownState,
|
|
4
|
+
NewContentMessage,
|
|
5
|
+
Peer,
|
|
6
|
+
SyncMessage,
|
|
7
|
+
} from "../sync";
|
|
8
|
+
|
|
9
|
+
function simplifySessions(msg: CoValueKnownState) {
|
|
10
|
+
const count = Object.values(msg.sessions).reduce(
|
|
11
|
+
(acc, session) => acc + session,
|
|
12
|
+
0,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
if (msg.header) {
|
|
16
|
+
return `header/${count}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return `empty`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function simplifyNewContent(content: NewContentMessage["new"]) {
|
|
23
|
+
if (!content) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Object.values(content)
|
|
28
|
+
.map((c) => `After: ${c.after} New: ${c.newTransactions.length}`)
|
|
29
|
+
.join(" | ");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function toSimplifiedMessages(
|
|
33
|
+
coValues: Record<string, CoValueCore>,
|
|
34
|
+
messages: {
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
msg: SyncMessage;
|
|
38
|
+
}[],
|
|
39
|
+
) {
|
|
40
|
+
function getCoValue(id: string) {
|
|
41
|
+
for (const [name, coValue] of Object.entries(coValues)) {
|
|
42
|
+
if (coValue.id === id) {
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return `unknown/${id}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toDebugString(from: string, to: string, msg: SyncMessage) {
|
|
51
|
+
switch (msg.action) {
|
|
52
|
+
case "known":
|
|
53
|
+
return `${from} -> ${to} | KNOWN ${msg.isCorrection ? "CORRECTION " : ""}${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
54
|
+
case "load":
|
|
55
|
+
return `${from} -> ${to} | LOAD ${getCoValue(msg.id)} sessions: ${simplifySessions(msg)}`;
|
|
56
|
+
case "done":
|
|
57
|
+
return `${from} -> ${to} | DONE ${getCoValue(msg.id)}`;
|
|
58
|
+
case "content":
|
|
59
|
+
return `${from} -> ${to} | CONTENT ${getCoValue(msg.id)} header: ${Boolean(msg.header)} new: ${simplifyNewContent(msg.new)}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return messages.map((m) => toDebugString(m.from, m.to, m.msg));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function debugMessages(
|
|
67
|
+
coValues: Record<string, CoValueCore>,
|
|
68
|
+
messages: {
|
|
69
|
+
from: string;
|
|
70
|
+
to: string;
|
|
71
|
+
msg: SyncMessage;
|
|
72
|
+
}[],
|
|
73
|
+
) {
|
|
74
|
+
console.log(toSimplifiedMessages(coValues, messages));
|
|
75
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { expectMap } from "../coValue";
|
|
4
|
+
import { toSimplifiedMessages } from "./messagesTestUtils";
|
|
5
|
+
import {
|
|
6
|
+
SyncMessagesLog,
|
|
7
|
+
loadCoValueOrFail,
|
|
8
|
+
setupTestNode,
|
|
9
|
+
waitFor,
|
|
10
|
+
} from "./testUtils";
|
|
11
|
+
|
|
12
|
+
let jazzCloud = setupTestNode({ isSyncServer: true });
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
SyncMessagesLog.clear();
|
|
16
|
+
jazzCloud = setupTestNode({ isSyncServer: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("loading coValues from server", () => {
|
|
20
|
+
test("coValue loading", async () => {
|
|
21
|
+
const { node: client } = setupTestNode({
|
|
22
|
+
connected: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const group = jazzCloud.node.createGroup();
|
|
26
|
+
const map = group.createMap();
|
|
27
|
+
map.set("hello", "world", "trusting");
|
|
28
|
+
|
|
29
|
+
const mapOnClient = await loadCoValueOrFail(client, map.id);
|
|
30
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
SyncMessagesLog.getMessages({
|
|
34
|
+
Group: group.core,
|
|
35
|
+
Map: map.core,
|
|
36
|
+
}),
|
|
37
|
+
).toMatchInlineSnapshot(`
|
|
38
|
+
[
|
|
39
|
+
"client -> server | LOAD Map sessions: empty",
|
|
40
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
|
|
41
|
+
"client -> server | KNOWN Group sessions: header/3",
|
|
42
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
43
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
44
|
+
]
|
|
45
|
+
`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("coValue with parent groups loading", async () => {
|
|
49
|
+
const client = setupTestNode({
|
|
50
|
+
connected: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const group = jazzCloud.node.createGroup();
|
|
54
|
+
const parentGroup = jazzCloud.node.createGroup();
|
|
55
|
+
parentGroup.addMember("everyone", "reader");
|
|
56
|
+
|
|
57
|
+
group.extend(parentGroup);
|
|
58
|
+
|
|
59
|
+
const map = group.createMap();
|
|
60
|
+
map.set("hello", "world");
|
|
61
|
+
|
|
62
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
63
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
64
|
+
|
|
65
|
+
expect(
|
|
66
|
+
SyncMessagesLog.getMessages({
|
|
67
|
+
ParentGroup: parentGroup.core,
|
|
68
|
+
Group: group.core,
|
|
69
|
+
Map: map.core,
|
|
70
|
+
}),
|
|
71
|
+
).toMatchInlineSnapshot(`
|
|
72
|
+
[
|
|
73
|
+
"client -> server | LOAD Map sessions: empty",
|
|
74
|
+
"server -> client | CONTENT ParentGroup header: true new: After: 0 New: 6",
|
|
75
|
+
"client -> server | KNOWN ParentGroup sessions: header/6",
|
|
76
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
77
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
78
|
+
"client -> server | KNOWN Group sessions: header/5",
|
|
79
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
80
|
+
]
|
|
81
|
+
`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("updating a coValue while offline", async () => {
|
|
85
|
+
const client = setupTestNode({
|
|
86
|
+
connected: false,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { peerState } = client.connectToSyncServer();
|
|
90
|
+
|
|
91
|
+
const group = jazzCloud.node.createGroup();
|
|
92
|
+
const map = group.createMap();
|
|
93
|
+
map.set("hello", "world", "trusting");
|
|
94
|
+
|
|
95
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
96
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
97
|
+
|
|
98
|
+
peerState.gracefulShutdown();
|
|
99
|
+
|
|
100
|
+
map.set("hello", "updated", "trusting");
|
|
101
|
+
|
|
102
|
+
SyncMessagesLog.clear();
|
|
103
|
+
client.connectToSyncServer();
|
|
104
|
+
|
|
105
|
+
await map.core.waitForSync();
|
|
106
|
+
|
|
107
|
+
expect(mapOnClient.get("hello")).toEqual("updated");
|
|
108
|
+
|
|
109
|
+
expect(
|
|
110
|
+
SyncMessagesLog.getMessages({
|
|
111
|
+
Group: group.core,
|
|
112
|
+
Map: map.core,
|
|
113
|
+
}),
|
|
114
|
+
).toMatchInlineSnapshot(`
|
|
115
|
+
[
|
|
116
|
+
"client -> server | LOAD Group sessions: header/3",
|
|
117
|
+
"server -> client | KNOWN Group sessions: header/3",
|
|
118
|
+
"client -> server | LOAD Map sessions: header/1",
|
|
119
|
+
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
|
|
120
|
+
"client -> server | KNOWN Map sessions: header/2",
|
|
121
|
+
]
|
|
122
|
+
`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("updating a coValue on both sides while offline", async () => {
|
|
126
|
+
const client = setupTestNode({});
|
|
127
|
+
|
|
128
|
+
const { peerState } = client.connectToSyncServer();
|
|
129
|
+
|
|
130
|
+
const group = jazzCloud.node.createGroup();
|
|
131
|
+
group.addMember("everyone", "writer");
|
|
132
|
+
|
|
133
|
+
const map = group.createMap({
|
|
134
|
+
fromServer: "initial",
|
|
135
|
+
fromClient: "initial",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
139
|
+
|
|
140
|
+
peerState.gracefulShutdown();
|
|
141
|
+
|
|
142
|
+
map.set("fromServer", "updated", "trusting");
|
|
143
|
+
mapOnClient.set("fromClient", "updated", "trusting");
|
|
144
|
+
|
|
145
|
+
SyncMessagesLog.clear();
|
|
146
|
+
client.connectToSyncServer();
|
|
147
|
+
|
|
148
|
+
await map.core.waitForSync();
|
|
149
|
+
await mapOnClient.core.waitForSync();
|
|
150
|
+
|
|
151
|
+
expect(mapOnClient.get("fromServer")).toEqual("updated");
|
|
152
|
+
expect(mapOnClient.get("fromClient")).toEqual("updated");
|
|
153
|
+
expect(
|
|
154
|
+
SyncMessagesLog.getMessages({
|
|
155
|
+
Group: group.core,
|
|
156
|
+
Map: map.core,
|
|
157
|
+
}),
|
|
158
|
+
).toMatchInlineSnapshot(`
|
|
159
|
+
[
|
|
160
|
+
"client -> server | LOAD Group sessions: header/5",
|
|
161
|
+
"server -> client | KNOWN Group sessions: header/5",
|
|
162
|
+
"client -> server | LOAD Map sessions: header/2",
|
|
163
|
+
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
|
|
164
|
+
"client -> server | KNOWN Map sessions: header/3",
|
|
165
|
+
"client -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
166
|
+
"server -> client | KNOWN Map sessions: header/3",
|
|
167
|
+
]
|
|
168
|
+
`);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("wrong optimistic known state should be corrected", async () => {
|
|
172
|
+
const client = setupTestNode({
|
|
173
|
+
connected: true,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const group = jazzCloud.node.createGroup();
|
|
177
|
+
group.addMember("everyone", "writer");
|
|
178
|
+
|
|
179
|
+
const map = group.createMap({
|
|
180
|
+
fromServer: "initial",
|
|
181
|
+
fromClient: "initial",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Load the coValue on the client
|
|
185
|
+
await loadCoValueOrFail(client.node, map.id);
|
|
186
|
+
|
|
187
|
+
// Forcefully delete the coValue from the client (simulating some data loss)
|
|
188
|
+
client.node.coValuesStore.coValues.delete(map.id);
|
|
189
|
+
|
|
190
|
+
map.set("fromServer", "updated", "trusting");
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
const coValue = expectMap(
|
|
194
|
+
client.node.expectCoValueLoaded(map.id).getCurrentContent(),
|
|
195
|
+
);
|
|
196
|
+
expect(coValue.get("fromServer")).toEqual("updated");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(
|
|
200
|
+
SyncMessagesLog.getMessages({
|
|
201
|
+
Group: group.core,
|
|
202
|
+
Map: map.core,
|
|
203
|
+
}),
|
|
204
|
+
).toMatchInlineSnapshot(`
|
|
205
|
+
[
|
|
206
|
+
"client -> server | LOAD Map sessions: empty",
|
|
207
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
208
|
+
"client -> server | KNOWN Group sessions: header/5",
|
|
209
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
210
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
211
|
+
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
|
|
212
|
+
"client -> server | KNOWN CORRECTION Map sessions: empty",
|
|
213
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 2",
|
|
214
|
+
"client -> server | KNOWN Map sessions: header/2",
|
|
215
|
+
]
|
|
216
|
+
`);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("unavailable coValue", async () => {
|
|
220
|
+
const group = jazzCloud.node.createGroup();
|
|
221
|
+
group.addMember("everyone", "writer");
|
|
222
|
+
|
|
223
|
+
const map = group.createMap({
|
|
224
|
+
fromServer: "initial",
|
|
225
|
+
fromClient: "initial",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Makes the CoValues unavailable on the server
|
|
229
|
+
jazzCloud.restart();
|
|
230
|
+
|
|
231
|
+
const client = setupTestNode({
|
|
232
|
+
connected: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Load the coValue on the client
|
|
236
|
+
const value = await client.node.load(map.id);
|
|
237
|
+
expect(value).toEqual("unavailable");
|
|
238
|
+
|
|
239
|
+
expect(
|
|
240
|
+
SyncMessagesLog.getMessages({
|
|
241
|
+
Group: group.core,
|
|
242
|
+
Map: map.core,
|
|
243
|
+
}),
|
|
244
|
+
).toMatchInlineSnapshot(`
|
|
245
|
+
[
|
|
246
|
+
"client -> server | LOAD Map sessions: empty",
|
|
247
|
+
"server -> client | KNOWN Map sessions: empty",
|
|
248
|
+
"client -> server | LOAD Map sessions: empty",
|
|
249
|
+
"server -> client | KNOWN Map sessions: empty",
|
|
250
|
+
]
|
|
251
|
+
`);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("large coValue streaming", async () => {
|
|
255
|
+
const group = jazzCloud.node.createGroup();
|
|
256
|
+
group.addMember("everyone", "writer");
|
|
257
|
+
|
|
258
|
+
const largeMap = group.createMap();
|
|
259
|
+
|
|
260
|
+
// Generate a large amount of data (about 100MB)
|
|
261
|
+
const dataSize = 1 * 1024 * 1024;
|
|
262
|
+
const chunkSize = 1024; // 1KB chunks
|
|
263
|
+
const chunks = dataSize / chunkSize;
|
|
264
|
+
|
|
265
|
+
const value = Buffer.alloc(chunkSize, `value$`).toString("base64");
|
|
266
|
+
|
|
267
|
+
for (let i = 0; i < chunks; i++) {
|
|
268
|
+
const key = `key${i}`;
|
|
269
|
+
largeMap.set(key, value, "trusting");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const client = setupTestNode({
|
|
273
|
+
connected: true,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await loadCoValueOrFail(client.node, largeMap.id);
|
|
277
|
+
|
|
278
|
+
await largeMap.core.waitForSync();
|
|
279
|
+
|
|
280
|
+
expect(
|
|
281
|
+
SyncMessagesLog.getMessages({
|
|
282
|
+
Group: group.core,
|
|
283
|
+
Map: largeMap.core,
|
|
284
|
+
}),
|
|
285
|
+
).toMatchInlineSnapshot(`
|
|
286
|
+
[
|
|
287
|
+
"client -> server | LOAD Map sessions: empty",
|
|
288
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
289
|
+
"client -> server | KNOWN Group sessions: header/5",
|
|
290
|
+
"server -> client | CONTENT Map header: true new: ",
|
|
291
|
+
"server -> client | CONTENT Map header: false new: After: 0 New: 73",
|
|
292
|
+
"client -> server | KNOWN Map sessions: header/0",
|
|
293
|
+
"server -> client | CONTENT Map header: false new: After: 73 New: 73",
|
|
294
|
+
"server -> client | CONTENT Map header: false new: After: 146 New: 73",
|
|
295
|
+
"client -> server | KNOWN Map sessions: header/73",
|
|
296
|
+
"server -> client | CONTENT Map header: false new: After: 219 New: 73",
|
|
297
|
+
"server -> client | CONTENT Map header: false new: After: 292 New: 73",
|
|
298
|
+
"client -> server | KNOWN Map sessions: header/146",
|
|
299
|
+
"server -> client | CONTENT Map header: false new: After: 365 New: 73",
|
|
300
|
+
"server -> client | CONTENT Map header: false new: After: 438 New: 73",
|
|
301
|
+
"client -> server | KNOWN Map sessions: header/219",
|
|
302
|
+
"server -> client | CONTENT Map header: false new: After: 511 New: 73",
|
|
303
|
+
"server -> client | CONTENT Map header: false new: After: 584 New: 73",
|
|
304
|
+
"client -> server | KNOWN Map sessions: header/292",
|
|
305
|
+
"server -> client | CONTENT Map header: false new: After: 657 New: 73",
|
|
306
|
+
"server -> client | CONTENT Map header: false new: After: 730 New: 73",
|
|
307
|
+
"client -> server | KNOWN Map sessions: header/365",
|
|
308
|
+
"server -> client | CONTENT Map header: false new: After: 803 New: 73",
|
|
309
|
+
"server -> client | CONTENT Map header: false new: After: 876 New: 73",
|
|
310
|
+
"client -> server | KNOWN Map sessions: header/438",
|
|
311
|
+
"server -> client | CONTENT Map header: false new: After: 949 New: 73",
|
|
312
|
+
"server -> client | CONTENT Map header: false new: After: 1022 New: 2",
|
|
313
|
+
"client -> server | KNOWN Map sessions: header/511",
|
|
314
|
+
"client -> server | KNOWN Map sessions: header/584",
|
|
315
|
+
"client -> server | KNOWN Map sessions: header/657",
|
|
316
|
+
"client -> server | KNOWN Map sessions: header/730",
|
|
317
|
+
"client -> server | KNOWN Map sessions: header/803",
|
|
318
|
+
"client -> server | KNOWN Map sessions: header/876",
|
|
319
|
+
"client -> server | KNOWN Map sessions: header/949",
|
|
320
|
+
"client -> server | KNOWN Map sessions: header/1022",
|
|
321
|
+
"client -> server | KNOWN Map sessions: header/1024",
|
|
322
|
+
]
|
|
323
|
+
`);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test.todo("should mark the coValue as unavailable if the peer is closed");
|
|
327
|
+
});
|