cojson 0.16.4 → 0.16.6
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 +23 -0
- package/dist/GarbageCollector.d.ts +12 -0
- package/dist/GarbageCollector.d.ts.map +1 -0
- package/dist/GarbageCollector.js +37 -0
- package/dist/GarbageCollector.js.map +1 -0
- package/dist/coValue.d.ts +1 -1
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +1 -0
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +11 -3
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +8 -11
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +30 -122
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/utils.d.ts.map +1 -1
- package/dist/coValueCore/utils.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +1 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coPlainText.d.ts +1 -0
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +27 -8
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +2 -2
- package/dist/coValues/group.d.ts +19 -11
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +221 -59
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -1
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +11 -4
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +8 -3
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +3 -3
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/localNode.d.ts +8 -5
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +11 -0
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
- package/dist/queue/StoreQueue.d.ts +7 -1
- package/dist/queue/StoreQueue.d.ts.map +1 -1
- package/dist/queue/StoreQueue.js +35 -13
- package/dist/queue/StoreQueue.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +4 -4
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +13 -4
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -3
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +12 -3
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +2 -7
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +2 -7
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +2 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +17 -3
- package/dist/sync.js.map +1 -1
- package/dist/tests/GarbageCollector.test.d.ts +2 -0
- package/dist/tests/GarbageCollector.test.d.ts.map +1 -0
- package/dist/tests/GarbageCollector.test.js +85 -0
- package/dist/tests/GarbageCollector.test.js.map +1 -0
- package/dist/tests/coPlainText.test.js +142 -4
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +3 -3
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/group.inheritance.test.js +195 -0
- package/dist/tests/group.inheritance.test.js.map +1 -1
- package/dist/tests/group.removeMember.test.js +152 -1
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/group.roleOf.test.js +2 -2
- package/dist/tests/group.roleOf.test.js.map +1 -1
- package/dist/tests/group.test.js +81 -3
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/sync.garbageCollection.test.d.ts +2 -0
- package/dist/tests/sync.garbageCollection.test.d.ts.map +1 -0
- package/dist/tests/sync.garbageCollection.test.js +133 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +6 -3
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +48 -34
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +31 -21
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +76 -29
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +1 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +1 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +2 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +6 -0
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts +2 -2
- package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts.map +1 -1
- package/dist/typeUtils/expectGroup.d.ts.map +1 -1
- package/dist/typeUtils/expectGroup.js +6 -5
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/GarbageCollector.ts +48 -0
- package/src/coValue.ts +1 -4
- package/src/coValueContentMessage.ts +16 -3
- package/src/coValueCore/coValueCore.ts +50 -198
- package/src/coValueCore/utils.ts +1 -0
- package/src/coValueCore/verifiedState.ts +1 -0
- package/src/coValues/coPlainText.ts +40 -8
- package/src/coValues/group.ts +310 -91
- package/src/config.ts +20 -1
- package/src/exports.ts +13 -5
- package/src/ids.ts +3 -3
- package/src/localNode.ts +22 -8
- package/src/queue/LocalTransactionsSyncQueue.ts +1 -1
- package/src/queue/StoreQueue.ts +45 -12
- package/src/storage/sqlite/client.ts +24 -10
- package/src/storage/sqliteAsync/client.ts +26 -5
- package/src/storage/storageAsync.ts +5 -9
- package/src/storage/storageSync.ts +2 -9
- package/src/storage/types.ts +7 -4
- package/src/sync.ts +19 -3
- package/src/tests/GarbageCollector.test.ts +127 -0
- package/src/tests/coPlainText.test.ts +176 -4
- package/src/tests/coStream.test.ts +7 -3
- package/src/tests/group.inheritance.test.ts +279 -0
- package/src/tests/group.removeMember.test.ts +244 -1
- package/src/tests/group.roleOf.test.ts +2 -2
- package/src/tests/group.test.ts +105 -5
- package/src/tests/sync.garbageCollection.test.ts +178 -0
- package/src/tests/sync.load.test.ts +6 -3
- package/src/tests/sync.mesh.test.ts +49 -34
- package/src/tests/sync.storage.test.ts +31 -21
- package/src/tests/sync.storageAsync.test.ts +81 -29
- package/src/tests/testStorage.ts +11 -3
- package/src/tests/testUtils.ts +9 -1
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -2
- package/src/typeUtils/expectGroup.ts +8 -5
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { setCoValueLoadingRetryDelay } from "../config.js";
|
|
2
3
|
import {
|
|
3
4
|
SyncMessagesLog,
|
|
4
|
-
|
|
5
|
+
blockMessageTypeOnOutgoingPeer,
|
|
5
6
|
loadCoValueOrFail,
|
|
6
7
|
setupTestAccount,
|
|
7
8
|
setupTestNode,
|
|
8
9
|
} from "./testUtils.js";
|
|
9
10
|
|
|
11
|
+
setCoValueLoadingRetryDelay(10);
|
|
12
|
+
|
|
10
13
|
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
11
14
|
|
|
12
15
|
beforeEach(async () => {
|
|
@@ -15,6 +18,65 @@ beforeEach(async () => {
|
|
|
15
18
|
});
|
|
16
19
|
|
|
17
20
|
describe("Group.removeMember", () => {
|
|
21
|
+
test("revoking a member access should not affect everyone access", async () => {
|
|
22
|
+
const admin = await setupTestAccount({
|
|
23
|
+
connected: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const alice = await setupTestAccount({
|
|
27
|
+
connected: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const group = admin.node.createGroup();
|
|
31
|
+
group.addMember("everyone", "writer");
|
|
32
|
+
|
|
33
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
34
|
+
admin.node,
|
|
35
|
+
alice.accountID,
|
|
36
|
+
);
|
|
37
|
+
group.addMember(aliceOnAdminNode, "writer");
|
|
38
|
+
group.removeMember(aliceOnAdminNode);
|
|
39
|
+
|
|
40
|
+
const groupOnAliceNode = await loadCoValueOrFail(alice.node, group.id);
|
|
41
|
+
expect(groupOnAliceNode.myRole()).toEqual("writer");
|
|
42
|
+
|
|
43
|
+
const map = groupOnAliceNode.createMap();
|
|
44
|
+
|
|
45
|
+
map.set("test", "test");
|
|
46
|
+
expect(map.get("test")).toEqual("test");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("revoking a member access should not affect everyone access when everyone access is gained through a group extension", async () => {
|
|
50
|
+
const admin = await setupTestAccount({
|
|
51
|
+
connected: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const alice = await setupTestAccount({
|
|
55
|
+
connected: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const parentGroup = admin.node.createGroup();
|
|
59
|
+
const group = admin.node.createGroup();
|
|
60
|
+
parentGroup.addMember("everyone", "reader");
|
|
61
|
+
group.extend(parentGroup);
|
|
62
|
+
|
|
63
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
64
|
+
admin.node,
|
|
65
|
+
alice.accountID,
|
|
66
|
+
);
|
|
67
|
+
group.addMember(aliceOnAdminNode, "writer");
|
|
68
|
+
group.removeMember(aliceOnAdminNode);
|
|
69
|
+
|
|
70
|
+
const map = group.createMap();
|
|
71
|
+
map.set("test", "test");
|
|
72
|
+
|
|
73
|
+
const groupOnAliceNode = await loadCoValueOrFail(alice.node, group.id);
|
|
74
|
+
expect(groupOnAliceNode.myRole()).toEqual("reader");
|
|
75
|
+
|
|
76
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
77
|
+
expect(mapOnAliceNode.get("test")).toEqual("test");
|
|
78
|
+
});
|
|
79
|
+
|
|
18
80
|
test("a reader member should be able to revoke themselves", async () => {
|
|
19
81
|
const admin = await setupTestAccount({
|
|
20
82
|
connected: true,
|
|
@@ -294,4 +356,185 @@ describe("Group.removeMember", () => {
|
|
|
294
356
|
undefined,
|
|
295
357
|
);
|
|
296
358
|
});
|
|
359
|
+
|
|
360
|
+
test("removing a member should rotate the readKey on available child groups", async () => {
|
|
361
|
+
const admin = await setupTestAccount({
|
|
362
|
+
connected: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const alice = await setupTestAccount({
|
|
366
|
+
connected: true,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
370
|
+
admin.node,
|
|
371
|
+
alice.accountID,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const group = admin.node.createGroup();
|
|
375
|
+
const childGroup = admin.node.createGroup();
|
|
376
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
377
|
+
|
|
378
|
+
childGroup.extend(group);
|
|
379
|
+
|
|
380
|
+
group.removeMember(aliceOnAdminNode);
|
|
381
|
+
|
|
382
|
+
const map = childGroup.createMap();
|
|
383
|
+
map.set("test", "Not readable by alice");
|
|
384
|
+
|
|
385
|
+
await map.core.waitForSync();
|
|
386
|
+
|
|
387
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
388
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("removing a member should rotate the readKey on unloaded child groups", async () => {
|
|
392
|
+
const admin = await setupTestAccount({
|
|
393
|
+
connected: true,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const bob = await setupTestAccount({
|
|
397
|
+
connected: true,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const alice = await setupTestAccount({
|
|
401
|
+
connected: true,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
405
|
+
|
|
406
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
407
|
+
admin.node,
|
|
408
|
+
alice.accountID,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const group = admin.node.createGroup();
|
|
412
|
+
|
|
413
|
+
const childGroup = bob.node.createGroup();
|
|
414
|
+
group.addMember(bobOnAdminNode, "reader");
|
|
415
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
416
|
+
|
|
417
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
418
|
+
|
|
419
|
+
childGroup.extend(groupOnBobNode);
|
|
420
|
+
|
|
421
|
+
await childGroup.core.waitForSync();
|
|
422
|
+
|
|
423
|
+
group.removeMember(aliceOnAdminNode);
|
|
424
|
+
|
|
425
|
+
// Rotating the child group keys is async when the child group is not loaded
|
|
426
|
+
await admin.node.getCoValue(childGroup.id).waitForAvailableOrUnavailable();
|
|
427
|
+
await admin.node.syncManager.waitForAllCoValuesSync();
|
|
428
|
+
|
|
429
|
+
const map = childGroup.createMap();
|
|
430
|
+
map.set("test", "Not readable by alice");
|
|
431
|
+
|
|
432
|
+
await map.core.waitForSync();
|
|
433
|
+
|
|
434
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
435
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("removing a member should work even if there are partially available child groups", async () => {
|
|
439
|
+
const admin = await setupTestAccount({
|
|
440
|
+
connected: true,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const bob = await setupTestAccount();
|
|
444
|
+
const { peer } = bob.connectToSyncServer();
|
|
445
|
+
|
|
446
|
+
const alice = await setupTestAccount({
|
|
447
|
+
connected: true,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
451
|
+
|
|
452
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
453
|
+
admin.node,
|
|
454
|
+
alice.accountID,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const group = admin.node.createGroup();
|
|
458
|
+
const childGroup = bob.node.createGroup();
|
|
459
|
+
|
|
460
|
+
group.addMember(bobOnAdminNode, "reader");
|
|
461
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
462
|
+
|
|
463
|
+
await group.core.waitForSync();
|
|
464
|
+
|
|
465
|
+
blockMessageTypeOnOutgoingPeer(peer, "content", {
|
|
466
|
+
id: childGroup.id,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
470
|
+
|
|
471
|
+
childGroup.extend(groupOnBobNode);
|
|
472
|
+
|
|
473
|
+
await groupOnBobNode.core.waitForSync();
|
|
474
|
+
|
|
475
|
+
group.removeMember(aliceOnAdminNode);
|
|
476
|
+
|
|
477
|
+
await admin.node.syncManager.waitForAllCoValuesSync();
|
|
478
|
+
|
|
479
|
+
const map = group.createMap();
|
|
480
|
+
map.set("test", "Not readable by alice");
|
|
481
|
+
|
|
482
|
+
await map.core.waitForSync();
|
|
483
|
+
|
|
484
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
485
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("removing a member should work even if there are unavailable child groups", async () => {
|
|
489
|
+
const admin = await setupTestAccount({
|
|
490
|
+
connected: true,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const { peerOnServer } = admin.connectToSyncServer();
|
|
494
|
+
|
|
495
|
+
const bob = await setupTestAccount({
|
|
496
|
+
connected: true,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const alice = await setupTestAccount({
|
|
500
|
+
connected: true,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const bobOnAdminNode = await loadCoValueOrFail(admin.node, bob.accountID);
|
|
504
|
+
|
|
505
|
+
const aliceOnAdminNode = await loadCoValueOrFail(
|
|
506
|
+
admin.node,
|
|
507
|
+
alice.accountID,
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const group = admin.node.createGroup();
|
|
511
|
+
const childGroup = bob.node.createGroup();
|
|
512
|
+
|
|
513
|
+
blockMessageTypeOnOutgoingPeer(peerOnServer, "content", {
|
|
514
|
+
id: childGroup.id,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
group.addMember(bobOnAdminNode, "reader");
|
|
518
|
+
group.addMember(aliceOnAdminNode, "reader");
|
|
519
|
+
|
|
520
|
+
await group.core.waitForSync();
|
|
521
|
+
|
|
522
|
+
const groupOnBobNode = await loadCoValueOrFail(bob.node, group.id);
|
|
523
|
+
|
|
524
|
+
childGroup.extend(groupOnBobNode);
|
|
525
|
+
|
|
526
|
+
await groupOnBobNode.core.waitForSync();
|
|
527
|
+
|
|
528
|
+
group.removeMember(aliceOnAdminNode);
|
|
529
|
+
|
|
530
|
+
await group.core.waitForSync();
|
|
531
|
+
|
|
532
|
+
const map = group.createMap();
|
|
533
|
+
map.set("test", "Not readable by alice");
|
|
534
|
+
|
|
535
|
+
await map.core.waitForSync();
|
|
536
|
+
|
|
537
|
+
const mapOnAliceNode = await loadCoValueOrFail(alice.node, map.id);
|
|
538
|
+
expect(mapOnAliceNode.get("test")).toBeUndefined();
|
|
539
|
+
});
|
|
297
540
|
});
|
|
@@ -33,7 +33,7 @@ describe("roleOf", () => {
|
|
|
33
33
|
const [agent2] = randomAgentAndSessionID();
|
|
34
34
|
|
|
35
35
|
group.addMember(agent2, "writer");
|
|
36
|
-
group.
|
|
36
|
+
group.removeMember(agent2);
|
|
37
37
|
expect(group.roleOfInternal(agent2.id)).toEqual(undefined);
|
|
38
38
|
});
|
|
39
39
|
|
|
@@ -63,7 +63,7 @@ describe("roleOf", () => {
|
|
|
63
63
|
|
|
64
64
|
group.addMemberInternal("everyone", "reader");
|
|
65
65
|
group.addMember(agent2, "writer");
|
|
66
|
-
group.
|
|
66
|
+
group.removeMember("everyone");
|
|
67
67
|
expect(group.roleOfInternal(agent2.id)).toEqual("writer");
|
|
68
68
|
expect(group.roleOfInternal("123" as RawAccountID)).toEqual(undefined);
|
|
69
69
|
});
|
package/src/tests/group.test.ts
CHANGED
|
@@ -3,18 +3,27 @@ import { RawCoList } from "../coValues/coList.js";
|
|
|
3
3
|
import { RawCoMap } from "../coValues/coMap.js";
|
|
4
4
|
import { RawCoStream } from "../coValues/coStream.js";
|
|
5
5
|
import { RawBinaryCoStream } from "../coValues/coStream.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import type { RawCoValue, RawGroup } from "../exports.js";
|
|
7
|
+
import type { NewContentMessage } from "../sync.js";
|
|
8
8
|
import {
|
|
9
9
|
createThreeConnectedNodes,
|
|
10
10
|
createTwoConnectedNodes,
|
|
11
11
|
loadCoValueOrFail,
|
|
12
12
|
nodeWithRandomAgentAndSessionID,
|
|
13
|
-
|
|
14
|
-
waitFor,
|
|
13
|
+
setupTestNode,
|
|
15
14
|
} from "./testUtils.js";
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
function expectGroup(content: RawCoValue): RawGroup {
|
|
17
|
+
if (content.type !== "comap") {
|
|
18
|
+
throw new Error("Expected group");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (content.core.verified.header.ruleset.type !== "group") {
|
|
22
|
+
throw new Error("Expected group ruleset in group");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return content as RawGroup;
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
test("Can create a RawCoMap in a group", () => {
|
|
20
29
|
const node = nodeWithRandomAgentAndSessionID();
|
|
@@ -307,6 +316,97 @@ test("Invites should have access to the new keys", async () => {
|
|
|
307
316
|
expect(mapOnNode2.get("test")).toEqual("Written from node1");
|
|
308
317
|
});
|
|
309
318
|
|
|
319
|
+
test("Should heal the missing key_for_everyone", async () => {
|
|
320
|
+
const client = setupTestNode({
|
|
321
|
+
secret:
|
|
322
|
+
"sealerSecret_zBTPp7U58Fzq9o7EvJpu4KEziepi8QVf2Xaxuy5xmmXFx/signerSecret_z62DuviZdXCjz4EZWofvr9vaLYFXDeTaC9KWhoQiQjzKk",
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const brokenGroupContent = {
|
|
326
|
+
action: "content",
|
|
327
|
+
id: "co_zW7F36Nnop9A7Er4gUzBcUXnZCK",
|
|
328
|
+
header: {
|
|
329
|
+
type: "comap",
|
|
330
|
+
ruleset: {
|
|
331
|
+
type: "group",
|
|
332
|
+
initialAdmin:
|
|
333
|
+
"sealer_z12QDazYB3ygPZtBV7sMm7iYKMRnNZ6Aaj1dfLXR7LSBm/signer_z2AskZQbc82qxo7iA3oiXoNExHLsAEXC2pHbwJzRnATWv",
|
|
334
|
+
},
|
|
335
|
+
meta: null,
|
|
336
|
+
createdAt: "2025-08-06T10:14:39.617Z",
|
|
337
|
+
uniqueness: "z3LJjnuPiPJaf5Qb9A",
|
|
338
|
+
},
|
|
339
|
+
priority: 0,
|
|
340
|
+
new: {
|
|
341
|
+
"sealer_z12QDazYB3ygPZtBV7sMm7iYKMRnNZ6Aaj1dfLXR7LSBm/signer_z2AskZQbc82qxo7iA3oiXoNExHLsAEXC2pHbwJzRnATWv_session_zYLsz2CiW9pW":
|
|
342
|
+
{
|
|
343
|
+
after: 0,
|
|
344
|
+
newTransactions: [
|
|
345
|
+
{
|
|
346
|
+
privacy: "trusting",
|
|
347
|
+
madeAt: 1754475279619,
|
|
348
|
+
changes:
|
|
349
|
+
'[{"key":"sealer_z12QDazYB3ygPZtBV7sMm7iYKMRnNZ6Aaj1dfLXR7LSBm/signer_z2AskZQbc82qxo7iA3oiXoNExHLsAEXC2pHbwJzRnATWv","op":"set","value":"admin"}]',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
privacy: "trusting",
|
|
353
|
+
madeAt: 1754475279621,
|
|
354
|
+
changes:
|
|
355
|
+
'[{"key":"key_z5CVahfMkEWPj1B3zH_for_sealer_z12QDazYB3ygPZtBV7sMm7iYKMRnNZ6Aaj1dfLXR7LSBm/signer_z2AskZQbc82qxo7iA3oiXoNExHLsAEXC2pHbwJzRnATWv","op":"set","value":"sealed_UCg4UkytXF-W8PaIvaDffO3pZ3d9hdXUuNkQQEikPTAuOD9us92Pqb5Vgu7lx1Fpb0X8V5BJ2yxz6_D5WOzK3qjWBSsc7J1xDJA=="}]',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
privacy: "trusting",
|
|
359
|
+
madeAt: 1754475279621,
|
|
360
|
+
changes:
|
|
361
|
+
'[{"key":"readKey","op":"set","value":"key_z5CVahfMkEWPj1B3zH"}]',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
privacy: "trusting",
|
|
365
|
+
madeAt: 1754475279622,
|
|
366
|
+
changes: '[{"key":"everyone","op":"set","value":"reader"}]',
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
privacy: "trusting",
|
|
370
|
+
madeAt: 1754475279623,
|
|
371
|
+
changes:
|
|
372
|
+
'[{"key":"key_z5CVahfMkEWPj1B3zH_for_everyone","op":"set","value":"keySecret_z9U9gzkahQXCxDoSw7isiUnbobXwuLdcSkL9Ci6ZEEkaL"}]',
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
privacy: "trusting",
|
|
376
|
+
madeAt: 1754475279623,
|
|
377
|
+
changes:
|
|
378
|
+
'[{"key":"key_z4Fi7hZNBx7XoVAKkP_for_sealer_z12QDazYB3ygPZtBV7sMm7iYKMRnNZ6Aaj1dfLXR7LSBm/signer_z2AskZQbc82qxo7iA3oiXoNExHLsAEXC2pHbwJzRnATWv","op":"set","value":"sealed_UuCBBfZkTnRTrGraqWWlzm9JE-VFduhsfu7WaZjpCbJYOTXpPhSNOnzGeS8qVuIsG6dORbME22lc5ltLxPjRqofQdDCNGQehCeQ=="}]',
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
privacy: "trusting",
|
|
382
|
+
madeAt: 1754475279624,
|
|
383
|
+
changes:
|
|
384
|
+
'[{"key":"key_z5CVahfMkEWPj1B3zH_for_key_z4Fi7hZNBx7XoVAKkP","op":"set","value":"encrypted_USTrBuobwTCORwy5yHxy4sFZ7swfrafP6k5ZwcTf76f0MBu9Ie-JmsX3mNXad4mluI47gvGXzi8I_"}]',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
privacy: "trusting",
|
|
388
|
+
madeAt: 1754475279624,
|
|
389
|
+
changes:
|
|
390
|
+
'[{"key":"readKey","op":"set","value":"key_z4Fi7hZNBx7XoVAKkP"}]',
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
lastSignature:
|
|
394
|
+
"signature_z3tsE7U1JaeNeUmZ4EY3Xq5uQ9jq9jDi6Rkhdt7T7b7z4NCnpMgB4bo8TwLXYVCrRdBm6PoyyPdK8fYFzHJUh5EzA",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
} as unknown as NewContentMessage;
|
|
398
|
+
|
|
399
|
+
client.node.syncManager.handleNewContent(brokenGroupContent, "import");
|
|
400
|
+
|
|
401
|
+
const group = expectGroup(
|
|
402
|
+
client.node.getCoValue(brokenGroupContent.id).getCurrentContent(),
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
expect(group.get(`${group.get("readKey")!}_for_everyone`)).toBe(
|
|
406
|
+
group.core.getCurrentReadKey()?.secret,
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
310
410
|
describe("writeOnly", () => {
|
|
311
411
|
test("Admins can invite writeOnly members", async () => {
|
|
312
412
|
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { setGarbageCollectorMaxAge } from "../config";
|
|
4
|
+
import { emptyKnownState } from "../exports";
|
|
5
|
+
import {
|
|
6
|
+
SyncMessagesLog,
|
|
7
|
+
TEST_NODE_CONFIG,
|
|
8
|
+
loadCoValueOrFail,
|
|
9
|
+
setupTestNode,
|
|
10
|
+
waitFor,
|
|
11
|
+
} from "./testUtils";
|
|
12
|
+
|
|
13
|
+
// We want to simulate a real world communication that happens asynchronously
|
|
14
|
+
TEST_NODE_CONFIG.withAsyncPeers = true;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// We want to test what happens when the garbage collector kicks in and removes a coValue
|
|
18
|
+
// We set the max age to -1 to make it remove everything
|
|
19
|
+
setGarbageCollectorMaxAge(-1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("sync after the garbage collector has run", () => {
|
|
23
|
+
let jazzCloud: ReturnType<typeof setupTestNode>;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
SyncMessagesLog.clear();
|
|
27
|
+
jazzCloud = setupTestNode({
|
|
28
|
+
isSyncServer: true,
|
|
29
|
+
});
|
|
30
|
+
jazzCloud.addStorage({
|
|
31
|
+
ourName: "server",
|
|
32
|
+
});
|
|
33
|
+
jazzCloud.node.enableGarbageCollector();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("loading a coValue from the sync server that was removed by the garbage collector", async () => {
|
|
37
|
+
const client = setupTestNode();
|
|
38
|
+
|
|
39
|
+
client.connectToSyncServer();
|
|
40
|
+
|
|
41
|
+
const group = jazzCloud.node.createGroup();
|
|
42
|
+
const map = group.createMap();
|
|
43
|
+
map.set("hello", "world", "trusting");
|
|
44
|
+
|
|
45
|
+
await map.core.waitForSync();
|
|
46
|
+
|
|
47
|
+
// force the garbage collector to run
|
|
48
|
+
jazzCloud.node.garbageCollector?.collect();
|
|
49
|
+
|
|
50
|
+
SyncMessagesLog.clear();
|
|
51
|
+
|
|
52
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
53
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
54
|
+
|
|
55
|
+
expect(
|
|
56
|
+
SyncMessagesLog.getMessages({
|
|
57
|
+
Group: group.core,
|
|
58
|
+
Map: map.core,
|
|
59
|
+
}),
|
|
60
|
+
).toMatchInlineSnapshot(`
|
|
61
|
+
[
|
|
62
|
+
"client -> server | LOAD Map sessions: empty",
|
|
63
|
+
"server -> storage | LOAD Map sessions: empty",
|
|
64
|
+
"storage -> server | CONTENT Group header: true new: After: 0 New: 3",
|
|
65
|
+
"storage -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
66
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
|
|
67
|
+
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
68
|
+
"client -> server | KNOWN Group sessions: header/3",
|
|
69
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
70
|
+
]
|
|
71
|
+
`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("updating a coValue that was removed by the garbage collector", async () => {
|
|
75
|
+
const client = setupTestNode();
|
|
76
|
+
|
|
77
|
+
client.connectToSyncServer();
|
|
78
|
+
|
|
79
|
+
const group = jazzCloud.node.createGroup();
|
|
80
|
+
group.addMember("everyone", "writer");
|
|
81
|
+
const map = group.createMap();
|
|
82
|
+
map.set("hello", "world", "trusting");
|
|
83
|
+
|
|
84
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
85
|
+
expect(mapOnClient.get("hello")).toEqual("world");
|
|
86
|
+
|
|
87
|
+
// force the garbage collector to run
|
|
88
|
+
jazzCloud.node.garbageCollector?.collect();
|
|
89
|
+
SyncMessagesLog.clear();
|
|
90
|
+
|
|
91
|
+
mapOnClient.set("hello", "updated", "trusting");
|
|
92
|
+
|
|
93
|
+
await mapOnClient.core.waitForSync();
|
|
94
|
+
|
|
95
|
+
const mapOnServer = await loadCoValueOrFail(jazzCloud.node, map.id);
|
|
96
|
+
|
|
97
|
+
expect(mapOnServer.get("hello")).toEqual("updated");
|
|
98
|
+
|
|
99
|
+
expect(
|
|
100
|
+
SyncMessagesLog.getMessages({
|
|
101
|
+
Group: group.core,
|
|
102
|
+
Map: map.core,
|
|
103
|
+
}),
|
|
104
|
+
).toMatchInlineSnapshot(`
|
|
105
|
+
[
|
|
106
|
+
"client -> server | CONTENT Map header: false new: After: 0 New: 1",
|
|
107
|
+
"server -> storage | LOAD Map sessions: empty",
|
|
108
|
+
"storage -> server | CONTENT Group header: true new: After: 0 New: 5",
|
|
109
|
+
"storage -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
110
|
+
"server -> client | KNOWN Map sessions: header/2",
|
|
111
|
+
"server -> storage | CONTENT Map header: false new: After: 0 New: 1",
|
|
112
|
+
]
|
|
113
|
+
`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("syncing a coValue that was removed by the garbage collector", async () => {
|
|
117
|
+
const edge = setupTestNode();
|
|
118
|
+
edge.addStorage({
|
|
119
|
+
ourName: "edge",
|
|
120
|
+
});
|
|
121
|
+
edge.connectToSyncServer({
|
|
122
|
+
syncServer: jazzCloud.node,
|
|
123
|
+
syncServerName: "server",
|
|
124
|
+
ourName: "edge",
|
|
125
|
+
});
|
|
126
|
+
edge.node.enableGarbageCollector();
|
|
127
|
+
const client = setupTestNode();
|
|
128
|
+
|
|
129
|
+
client.connectToSyncServer({
|
|
130
|
+
syncServer: edge.node,
|
|
131
|
+
syncServerName: "edge",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const group = edge.node.createGroup();
|
|
135
|
+
group.addMember("everyone", "writer");
|
|
136
|
+
|
|
137
|
+
await group.core.waitForSync();
|
|
138
|
+
|
|
139
|
+
const map = group.createMap();
|
|
140
|
+
|
|
141
|
+
map.set("hello", "updated", "trusting");
|
|
142
|
+
|
|
143
|
+
// force the garbage collector to run before the transaction is synced
|
|
144
|
+
edge.node.garbageCollector?.collect();
|
|
145
|
+
expect(edge.node.getCoValue(map.id).isAvailable()).toBe(false);
|
|
146
|
+
|
|
147
|
+
SyncMessagesLog.clear();
|
|
148
|
+
|
|
149
|
+
// The storage should work even after the coValue is unmounted, so the load should be successful
|
|
150
|
+
const mapOnClient = await loadCoValueOrFail(client.node, map.id);
|
|
151
|
+
expect(mapOnClient.get("hello")).toEqual("updated");
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
SyncMessagesLog.getMessages({
|
|
155
|
+
Group: group.core,
|
|
156
|
+
Map: map.core,
|
|
157
|
+
}),
|
|
158
|
+
).toMatchInlineSnapshot(`
|
|
159
|
+
[
|
|
160
|
+
"client -> edge | LOAD Map sessions: empty",
|
|
161
|
+
"edge -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
162
|
+
"edge -> server | CONTENT Map header: true new: After: 0 New: 1",
|
|
163
|
+
"edge -> storage | LOAD Map sessions: empty",
|
|
164
|
+
"storage -> edge | CONTENT Group header: true new: After: 0 New: 5",
|
|
165
|
+
"storage -> edge | CONTENT Map header: true new: After: 0 New: 1",
|
|
166
|
+
"edge -> server | CONTENT Map header: true new: ",
|
|
167
|
+
"edge -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
168
|
+
"edge -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
169
|
+
"server -> edge | KNOWN Map sessions: header/1",
|
|
170
|
+
"server -> storage | CONTENT Map header: true new: After: 0 New: 1",
|
|
171
|
+
"server -> edge | KNOWN Map sessions: header/1",
|
|
172
|
+
"server -> storage | CONTENT Map header: true new: ",
|
|
173
|
+
"client -> edge | KNOWN Group sessions: header/5",
|
|
174
|
+
"client -> edge | KNOWN Map sessions: header/1",
|
|
175
|
+
]
|
|
176
|
+
`);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -590,11 +590,14 @@ describe("loading coValues from server", () => {
|
|
|
590
590
|
"client -> server | LOAD Map sessions: empty",
|
|
591
591
|
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
592
592
|
"client -> server | KNOWN Group sessions: header/5",
|
|
593
|
-
"
|
|
594
|
-
"
|
|
593
|
+
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
|
|
594
|
+
"client -> server | KNOWN Group sessions: header/5",
|
|
595
595
|
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
596
|
-
"client -> server | LOAD Group sessions: header/5",
|
|
597
596
|
"client -> server | KNOWN Map sessions: header/1",
|
|
597
|
+
"client -> server | LOAD Group sessions: header/5",
|
|
598
|
+
"server -> client | KNOWN Group sessions: header/5",
|
|
599
|
+
"client -> server | LOAD Map sessions: header/1",
|
|
600
|
+
"server -> client | KNOWN Map sessions: header/1",
|
|
598
601
|
]
|
|
599
602
|
`);
|
|
600
603
|
});
|