cojson 0.18.19 → 0.18.20
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 +12 -0
- package/dist/GarbageCollector.d.ts +2 -1
- package/dist/GarbageCollector.d.ts.map +1 -1
- package/dist/GarbageCollector.js +3 -2
- package/dist/GarbageCollector.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +28 -25
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +128 -90
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/utils.d.ts +6 -0
- package/dist/coValueCore/utils.d.ts.map +1 -1
- package/dist/coValueCore/utils.js +53 -25
- package/dist/coValueCore/utils.js.map +1 -1
- package/dist/localNode.d.ts +5 -4
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +31 -37
- package/dist/localNode.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +56 -69
- package/dist/sync.js.map +1 -1
- package/dist/tests/GarbageCollector.test.js +14 -0
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +1 -1
- package/dist/tests/coValueCore.dependencies.test.d.ts +2 -0
- package/dist/tests/coValueCore.dependencies.test.d.ts.map +1 -0
- package/dist/tests/coValueCore.dependencies.test.js +55 -0
- package/dist/tests/coValueCore.dependencies.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +2 -2
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.js +43 -62
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -1
- package/dist/tests/permissions.test.js +117 -117
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +238 -9
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +7 -6
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +2 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/GarbageCollector.ts +5 -2
- package/src/coValueCore/coValueCore.ts +172 -118
- package/src/coValueCore/utils.ts +85 -31
- package/src/localNode.ts +43 -48
- package/src/sync.ts +63 -89
- package/src/tests/GarbageCollector.test.ts +20 -0
- package/src/tests/SyncStateManager.test.ts +1 -1
- package/src/tests/coValueCore.dependencies.test.ts +90 -0
- package/src/tests/coValueCore.test.ts +2 -2
- package/src/tests/coValueCoreLoadingState.test.ts +50 -66
- package/src/tests/permissions.test.ts +120 -123
- package/src/tests/sync.load.test.ts +308 -9
- package/src/tests/sync.peerReconciliation.test.ts +7 -6
- package/src/tests/testUtils.ts +5 -3
package/src/localNode.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { Peer, PeerID, SyncManager } from "./sync.js";
|
|
|
40
40
|
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
41
41
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
42
42
|
import { canBeBranched } from "./coValueCore/branching.js";
|
|
43
|
+
import { connectedPeers } from "./streamUtils.js";
|
|
43
44
|
|
|
44
45
|
/** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
|
|
45
46
|
|
|
@@ -81,12 +82,15 @@ export class LocalNode {
|
|
|
81
82
|
this.crypto = crypto;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
enableGarbageCollector() {
|
|
85
|
+
enableGarbageCollector(opts?: { garbageCollectGroups?: boolean }) {
|
|
85
86
|
if (this.garbageCollector) {
|
|
86
87
|
return;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
this.garbageCollector = new GarbageCollector(
|
|
90
|
+
this.garbageCollector = new GarbageCollector(
|
|
91
|
+
this.coValues,
|
|
92
|
+
opts?.garbageCollectGroups ?? false,
|
|
93
|
+
);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
setStorage(storage: StorageAPI) {
|
|
@@ -99,7 +103,13 @@ export class LocalNode {
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
hasCoValue(id: RawCoID) {
|
|
102
|
-
|
|
106
|
+
const coValue = this.coValues.get(id);
|
|
107
|
+
|
|
108
|
+
if (!coValue) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return coValue.loadingState !== "unknown";
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
getCoValue(id: RawCoID) {
|
|
@@ -119,16 +129,6 @@ export class LocalNode {
|
|
|
119
129
|
return this.coValues.values();
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
private putCoValue(
|
|
123
|
-
id: RawCoID,
|
|
124
|
-
verified: VerifiedState,
|
|
125
|
-
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
126
|
-
): AvailableCoValueCore {
|
|
127
|
-
const entry = this.getCoValue(id);
|
|
128
|
-
entry.internalMarkMagicallyAvailable(verified, { forceOverwrite });
|
|
129
|
-
return entry as AvailableCoValueCore;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
132
|
internalDeleteCoValue(id: RawCoID) {
|
|
133
133
|
this.coValues.delete(id);
|
|
134
134
|
}
|
|
@@ -371,10 +371,13 @@ export class LocalNode {
|
|
|
371
371
|
|
|
372
372
|
const id = idforHeader(header, this.crypto);
|
|
373
373
|
|
|
374
|
-
const coValue = this.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
374
|
+
const coValue = this.getCoValue(id);
|
|
375
|
+
|
|
376
|
+
coValue.provideHeader(header);
|
|
377
|
+
|
|
378
|
+
if (!coValue.hasVerifiedContent()) {
|
|
379
|
+
throw new Error("CoValue not available after providing header");
|
|
380
|
+
}
|
|
378
381
|
|
|
379
382
|
this.garbageCollector?.trackCoValueAccess(coValue);
|
|
380
383
|
this.syncManager.syncHeader(coValue.verified);
|
|
@@ -624,11 +627,13 @@ export class LocalNode {
|
|
|
624
627
|
return;
|
|
625
628
|
}
|
|
626
629
|
|
|
630
|
+
const groupCoreAsDifferentAgent = await this.loadCoValueAsDifferentAgent(
|
|
631
|
+
group.id,
|
|
632
|
+
inviteAgentSecret,
|
|
633
|
+
);
|
|
634
|
+
|
|
627
635
|
const groupAsInvite = expectGroup(
|
|
628
|
-
|
|
629
|
-
group.id,
|
|
630
|
-
inviteAgentSecret,
|
|
631
|
-
).getCurrentContent(),
|
|
636
|
+
groupCoreAsDifferentAgent.getCurrentContent(),
|
|
632
637
|
);
|
|
633
638
|
|
|
634
639
|
groupAsInvite.addMemberInternal(
|
|
@@ -755,7 +760,7 @@ export class LocalNode {
|
|
|
755
760
|
return group;
|
|
756
761
|
}
|
|
757
762
|
|
|
758
|
-
loadCoValueAsDifferentAgent(
|
|
763
|
+
async loadCoValueAsDifferentAgent(
|
|
759
764
|
id: RawCoID,
|
|
760
765
|
secret: AgentSecret,
|
|
761
766
|
accountId?: RawAccountID | AgentID,
|
|
@@ -768,44 +773,34 @@ export class LocalNode {
|
|
|
768
773
|
this.crypto,
|
|
769
774
|
);
|
|
770
775
|
|
|
771
|
-
newNode.
|
|
776
|
+
await newNode.loadVerifiedStateFrom(this, id);
|
|
772
777
|
|
|
773
778
|
return newNode.expectCoValueLoaded(id);
|
|
774
779
|
}
|
|
775
780
|
|
|
776
781
|
/** @internal */
|
|
777
|
-
|
|
778
|
-
const
|
|
782
|
+
async loadVerifiedStateFrom(otherNode: LocalNode, id: RawCoID) {
|
|
783
|
+
const connection = connectedPeers("source-" + id, "target-" + id, {
|
|
784
|
+
peer1role: "server",
|
|
785
|
+
peer2role: "client",
|
|
786
|
+
});
|
|
779
787
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const coValueID = coValuesIdsToCopy[i]!;
|
|
783
|
-
const coValue = otherNode.getCoValue(coValueID);
|
|
788
|
+
this.syncManager.addPeer(connection[0], true);
|
|
789
|
+
otherNode.syncManager.addPeer(connection[1], true);
|
|
784
790
|
|
|
785
|
-
|
|
786
|
-
continue;
|
|
787
|
-
}
|
|
791
|
+
const coValue = this.getCoValue(id);
|
|
788
792
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
793
|
+
const peerState = this.syncManager.peers[connection[0].id];
|
|
794
|
+
|
|
795
|
+
if (!peerState) {
|
|
796
|
+
throw new Error("Peer state not found");
|
|
792
797
|
}
|
|
793
798
|
|
|
794
|
-
|
|
795
|
-
while (coValuesIdsToCopy.length > 0) {
|
|
796
|
-
const coValueID = coValuesIdsToCopy.pop()!;
|
|
797
|
-
const coValue = otherNode.getCoValue(coValueID);
|
|
799
|
+
coValue.loadFromPeers([peerState]);
|
|
798
800
|
|
|
799
|
-
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
801
|
+
await coValue.waitForAvailable();
|
|
802
802
|
|
|
803
|
-
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
this.putCoValue(coValueID, coValue.verified);
|
|
808
|
-
}
|
|
803
|
+
peerState.gracefulShutdown();
|
|
809
804
|
}
|
|
810
805
|
|
|
811
806
|
/**
|
package/src/sync.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
knownStateFromContent,
|
|
9
9
|
} from "./coValueContentMessage.js";
|
|
10
10
|
import { CoValueCore } from "./coValueCore/coValueCore.js";
|
|
11
|
-
import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
|
|
12
11
|
import { CoValueHeader, Transaction } from "./coValueCore/verifiedState.js";
|
|
13
12
|
import { Signature } from "./crypto/crypto.js";
|
|
14
13
|
import { RawCoID, SessionID, isRawCoID } from "./ids.js";
|
|
@@ -17,8 +16,6 @@ import { logger } from "./logger.js";
|
|
|
17
16
|
import { CoValuePriority } from "./priority.js";
|
|
18
17
|
import { IncomingMessagesQueue } from "./queue/IncomingMessagesQueue.js";
|
|
19
18
|
import { LocalTransactionsSyncQueue } from "./queue/LocalTransactionsSyncQueue.js";
|
|
20
|
-
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
21
|
-
import { isAccountID } from "./typeUtils/isAccountID.js";
|
|
22
19
|
|
|
23
20
|
export type CoValueKnownState = {
|
|
24
21
|
id: RawCoID;
|
|
@@ -463,7 +460,6 @@ export class SyncManager {
|
|
|
463
460
|
|
|
464
461
|
const handleLoadResult = () => {
|
|
465
462
|
if (coValue.isAvailable()) {
|
|
466
|
-
this.sendNewContent(msg.id, peer);
|
|
467
463
|
return;
|
|
468
464
|
}
|
|
469
465
|
|
|
@@ -524,8 +520,42 @@ export class SyncManager {
|
|
|
524
520
|
? "import"
|
|
525
521
|
: peer?.role;
|
|
526
522
|
|
|
523
|
+
coValue.addDependenciesFromContentMessage(msg);
|
|
524
|
+
|
|
525
|
+
// If some of the dependencies are missing, we wait for them to be available
|
|
526
|
+
// before handling the new content
|
|
527
|
+
// This must happen even if the dependencies are not related to this content
|
|
528
|
+
// but the content we've got before
|
|
529
|
+
if (!this.skipVerify && coValue.hasMissingDependencies()) {
|
|
530
|
+
coValue.addNewContentToQueue(msg, from);
|
|
531
|
+
|
|
532
|
+
for (const dependency of coValue.missingDependencies) {
|
|
533
|
+
const dependencyCoValue = this.local.getCoValue(dependency);
|
|
534
|
+
if (!dependencyCoValue.hasVerifiedContent()) {
|
|
535
|
+
const peers = this.getServerPeers(dependency);
|
|
536
|
+
|
|
537
|
+
// If the peer that sent the new content is a client, we can assume that they are in possession of the dependency
|
|
538
|
+
if (peer?.role === "client") {
|
|
539
|
+
peers.push(peer);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
dependencyCoValue.load(peers);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Check if we have the CoValue in memory
|
|
551
|
+
*/
|
|
527
552
|
if (!coValue.hasVerifiedContent()) {
|
|
553
|
+
/**
|
|
554
|
+
* The peer has assumed we already have the CoValue
|
|
555
|
+
*/
|
|
528
556
|
if (!msg.header) {
|
|
557
|
+
// We check if the covalue was in memory and has been garbage collected
|
|
558
|
+
// In that case we should have it tracked in the storage
|
|
529
559
|
const storageKnownState = this.local.storage?.getKnownState(msg.id);
|
|
530
560
|
|
|
531
561
|
if (storageKnownState?.header) {
|
|
@@ -542,6 +572,7 @@ export class SyncManager {
|
|
|
542
572
|
return;
|
|
543
573
|
}
|
|
544
574
|
|
|
575
|
+
// The peer assumption is not correct, so we ask for the full CoValue
|
|
545
576
|
if (peer) {
|
|
546
577
|
this.trySendToPeer(peer, {
|
|
547
578
|
action: "known",
|
|
@@ -551,6 +582,8 @@ export class SyncManager {
|
|
|
551
582
|
sessions: {},
|
|
552
583
|
});
|
|
553
584
|
} else {
|
|
585
|
+
// The wrong assumption has been made by storage or import, we don't have a recovery mechanism
|
|
586
|
+
// Should never happen
|
|
554
587
|
logger.error(
|
|
555
588
|
"Received new content with no header on a missing CoValue",
|
|
556
589
|
{
|
|
@@ -561,43 +594,15 @@ export class SyncManager {
|
|
|
561
594
|
return;
|
|
562
595
|
}
|
|
563
596
|
|
|
564
|
-
const
|
|
565
|
-
const transactions = Object.values(msg.new).map(
|
|
566
|
-
(content) => content.newTransactions,
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
// If we'll be performing transaction verification, ensure all the dependencies available.
|
|
570
|
-
if (!this.skipVerify) {
|
|
571
|
-
for (const dependency of getDependedOnCoValuesFromRawData(
|
|
572
|
-
msg.id,
|
|
573
|
-
msg.header,
|
|
574
|
-
sessionIDs,
|
|
575
|
-
transactions,
|
|
576
|
-
)) {
|
|
577
|
-
const dependencyCoValue = this.local.getCoValue(dependency);
|
|
578
|
-
|
|
579
|
-
if (!dependencyCoValue.hasVerifiedContent()) {
|
|
580
|
-
coValue.markMissingDependency(dependency);
|
|
581
|
-
|
|
582
|
-
const peers = this.getServerPeers(dependencyCoValue.id);
|
|
583
|
-
|
|
584
|
-
// if the peer that sent the content is a client, we add it to the list of peers
|
|
585
|
-
// to also ask them for the dependency
|
|
586
|
-
if (peer?.role === "client") {
|
|
587
|
-
peers.push(peer);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
dependencyCoValue.load(peers);
|
|
591
|
-
} else if (!dependencyCoValue.isAvailable()) {
|
|
592
|
-
coValue.markMissingDependency(dependency);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
597
|
+
const previousState = coValue.loadingState;
|
|
596
598
|
|
|
599
|
+
/**
|
|
600
|
+
* We are getting the full CoValue, so we can instantiate it
|
|
601
|
+
*/
|
|
597
602
|
const success = coValue.provideHeader(
|
|
598
603
|
msg.header,
|
|
599
|
-
peer?.id ?? "storage",
|
|
600
604
|
msg.expectContentUntil,
|
|
605
|
+
this.skipVerify,
|
|
601
606
|
);
|
|
602
607
|
|
|
603
608
|
if (!success) {
|
|
@@ -608,6 +613,7 @@ export class SyncManager {
|
|
|
608
613
|
return;
|
|
609
614
|
}
|
|
610
615
|
|
|
616
|
+
coValue.markFoundInPeer(peer?.id ?? "storage", previousState);
|
|
611
617
|
peer?.updateHeader(msg.id, true);
|
|
612
618
|
|
|
613
619
|
if (msg.expectContentUntil) {
|
|
@@ -619,6 +625,7 @@ export class SyncManager {
|
|
|
619
625
|
}
|
|
620
626
|
}
|
|
621
627
|
|
|
628
|
+
// At this point the CoValue must be in memory, if not we have a bug
|
|
622
629
|
if (!coValue.hasVerifiedContent()) {
|
|
623
630
|
throw new Error(
|
|
624
631
|
"Unreachable: CoValue should always have a verified state at this point",
|
|
@@ -635,6 +642,9 @@ export class SyncManager {
|
|
|
635
642
|
new: {},
|
|
636
643
|
};
|
|
637
644
|
|
|
645
|
+
/**
|
|
646
|
+
* The coValue is in memory, load the transactions from the content message
|
|
647
|
+
*/
|
|
638
648
|
for (const [sessionID, newContentForSession] of Object.entries(msg.new) as [
|
|
639
649
|
SessionID,
|
|
640
650
|
SessionNewContent,
|
|
@@ -659,57 +669,8 @@ export class SyncManager {
|
|
|
659
669
|
continue;
|
|
660
670
|
}
|
|
661
671
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
const accountId = accountOrAgentIDfromSessionID(sessionID);
|
|
665
|
-
|
|
666
|
-
if (isAccountID(accountId)) {
|
|
667
|
-
const account = this.local.getCoValue(accountId);
|
|
668
|
-
|
|
669
|
-
// We can't verify the transaction without the account, so we delay the session content handling until the account is available
|
|
670
|
-
if (!account.isAvailable()) {
|
|
671
|
-
// This covers the case where we are getting a new session on an already loaded coValue
|
|
672
|
-
// where we need to load the account to get their public key
|
|
673
|
-
if (!coValue.missingDependencies.has(accountId)) {
|
|
674
|
-
const peers = this.getServerPeers(account.id);
|
|
675
|
-
|
|
676
|
-
if (peer?.role === "client") {
|
|
677
|
-
// if the peer that sent the content is a client, we add it to the list of peers
|
|
678
|
-
// to also ask them for the dependency
|
|
679
|
-
peers.push(peer);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
account.load(peers);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// We need to wait for the account to be available before we can verify the transaction
|
|
686
|
-
// Currently doing this by delaying the handleNewContent for the session to when we have the account
|
|
687
|
-
//
|
|
688
|
-
// This is not the best solution, because the knownState is not updated and the ACK response will be given
|
|
689
|
-
// by excluding the session.
|
|
690
|
-
// This is good enough implementation for now because the only case for the account to be missing are out-of-order
|
|
691
|
-
// dependencies push, so the gap should be short lived.
|
|
692
|
-
//
|
|
693
|
-
// When we are going to have sharded-peers we should revisit this, and store unverified sessions that are considered as part of the
|
|
694
|
-
// knwonState, but not actively used until they can be verified.
|
|
695
|
-
void account.waitForAvailable().then(() => {
|
|
696
|
-
this.handleNewContent(
|
|
697
|
-
{
|
|
698
|
-
action: "content",
|
|
699
|
-
id: coValue.id,
|
|
700
|
-
new: {
|
|
701
|
-
[sessionID]: newContentForSession,
|
|
702
|
-
},
|
|
703
|
-
priority: msg.priority,
|
|
704
|
-
},
|
|
705
|
-
from,
|
|
706
|
-
);
|
|
707
|
-
});
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
672
|
+
// TODO: Handle invalid signatures in the middle of streaming
|
|
673
|
+
// This could cause a situation where we are unable to load a chunk, and ask for a correction for all the subsequent chunks
|
|
713
674
|
const result = coValue.tryAddTransactions(
|
|
714
675
|
sessionID,
|
|
715
676
|
newTransactions,
|
|
@@ -725,6 +686,7 @@ export class SyncManager {
|
|
|
725
686
|
id: msg.id,
|
|
726
687
|
err: result.error,
|
|
727
688
|
});
|
|
689
|
+
// TODO Mark only the session as errored, not the whole coValue
|
|
728
690
|
coValue.markErrored(peer.id, result.error);
|
|
729
691
|
} else {
|
|
730
692
|
logger.error("Failed to add transactions from storage", {
|
|
@@ -740,7 +702,10 @@ export class SyncManager {
|
|
|
740
702
|
}
|
|
741
703
|
|
|
742
704
|
// The new content for this session has been verified, so we can store it
|
|
743
|
-
|
|
705
|
+
if (result.value) {
|
|
706
|
+
contentToStore.new[sessionID] = newContentForSession;
|
|
707
|
+
}
|
|
708
|
+
|
|
744
709
|
peer?.updateSessionCounter(
|
|
745
710
|
msg.id,
|
|
746
711
|
sessionID,
|
|
@@ -749,6 +714,9 @@ export class SyncManager {
|
|
|
749
714
|
);
|
|
750
715
|
}
|
|
751
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Check if we lack some transactions to be able to load the new content
|
|
719
|
+
*/
|
|
752
720
|
if (invalidStateAssumed) {
|
|
753
721
|
if (peer) {
|
|
754
722
|
this.trySendToPeer(peer, {
|
|
@@ -784,6 +752,9 @@ export class SyncManager {
|
|
|
784
752
|
|
|
785
753
|
const syncedPeers = [];
|
|
786
754
|
|
|
755
|
+
/**
|
|
756
|
+
* Store the content and propagate it to the server peers and the subscribed client peers
|
|
757
|
+
*/
|
|
787
758
|
const hasNewContent =
|
|
788
759
|
contentToStore.header || Object.keys(contentToStore.new).length > 0;
|
|
789
760
|
|
|
@@ -824,6 +795,9 @@ export class SyncManager {
|
|
|
824
795
|
}
|
|
825
796
|
}
|
|
826
797
|
|
|
798
|
+
/**
|
|
799
|
+
* Send an update to all the sync state listeners
|
|
800
|
+
*/
|
|
827
801
|
for (const peer of syncedPeers) {
|
|
828
802
|
this.syncState.triggerUpdate(peer.id, coValue.id);
|
|
829
803
|
}
|
|
@@ -69,6 +69,26 @@ describe("garbage collector", () => {
|
|
|
69
69
|
test("coValues are not garbage collected if they are a group or account", async () => {
|
|
70
70
|
const client = await setupTestAccount();
|
|
71
71
|
|
|
72
|
+
client.addStorage({
|
|
73
|
+
ourName: "client",
|
|
74
|
+
});
|
|
75
|
+
client.node.enableGarbageCollector({
|
|
76
|
+
garbageCollectGroups: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const group = client.node.createGroup();
|
|
80
|
+
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
82
|
+
|
|
83
|
+
client.node.garbageCollector?.collect();
|
|
84
|
+
|
|
85
|
+
expect(client.node.getCoValue(group.id).isAvailable()).toBe(false);
|
|
86
|
+
expect(client.node.getCoValue(client.accountID).isAvailable()).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("group or account coValues are garbage collected if garbageCollectGroups is true", async () => {
|
|
90
|
+
const client = await setupTestAccount();
|
|
91
|
+
|
|
72
92
|
client.addStorage({
|
|
73
93
|
ourName: "client",
|
|
74
94
|
});
|
|
@@ -333,9 +333,9 @@ describe("SyncStateManager", () => {
|
|
|
333
333
|
[
|
|
334
334
|
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
|
|
335
335
|
"client -> server | LOAD Group sessions: empty",
|
|
336
|
-
"client -> server | KNOWN Map sessions: header/1",
|
|
337
336
|
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
|
|
338
337
|
"client -> server | KNOWN Group sessions: header/3",
|
|
338
|
+
"client -> server | KNOWN Map sessions: header/1",
|
|
339
339
|
]
|
|
340
340
|
`);
|
|
341
341
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { beforeEach, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
loadCoValueOrFail,
|
|
4
|
+
setupTestAccount,
|
|
5
|
+
setupTestNode,
|
|
6
|
+
} from "./testUtils.js";
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
setupTestNode({ isSyncServer: true });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("should track the group dependency when creating a new coValue", () => {
|
|
13
|
+
const client = setupTestNode();
|
|
14
|
+
|
|
15
|
+
const group = client.node.createGroup();
|
|
16
|
+
const map = group.createMap();
|
|
17
|
+
|
|
18
|
+
expect(map.core.getDependedOnCoValues()).toEqual(new Set([group.core.id]));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("should track the source dependency when creating a new branch", () => {
|
|
22
|
+
const client = setupTestNode();
|
|
23
|
+
|
|
24
|
+
const group = client.node.createGroup();
|
|
25
|
+
const map = group.createMap();
|
|
26
|
+
|
|
27
|
+
const branch = map.core.createBranch("feature-branch", group.id);
|
|
28
|
+
|
|
29
|
+
expect(branch.getDependedOnCoValues()).toEqual(
|
|
30
|
+
new Set([map.core.id, group.core.id]),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should track the parent group dependency when extending a group", () => {
|
|
35
|
+
const client = setupTestNode();
|
|
36
|
+
|
|
37
|
+
const group = client.node.createGroup();
|
|
38
|
+
const parentGroup = client.node.createGroup();
|
|
39
|
+
|
|
40
|
+
group.extend(parentGroup);
|
|
41
|
+
|
|
42
|
+
expect(group.core.getDependedOnCoValues()).toEqual(
|
|
43
|
+
new Set([parentGroup.core.id]),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("should track the account dependency when syncing an account session", async () => {
|
|
48
|
+
const sourceClient = await setupTestAccount({
|
|
49
|
+
connected: true,
|
|
50
|
+
});
|
|
51
|
+
const targetClient = await setupTestAccount({
|
|
52
|
+
connected: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const group = sourceClient.node.createGroup();
|
|
56
|
+
group.addMember("everyone", "reader");
|
|
57
|
+
const map = group.createMap();
|
|
58
|
+
|
|
59
|
+
map.set("hello", "world");
|
|
60
|
+
|
|
61
|
+
await map.core.waitForSync();
|
|
62
|
+
|
|
63
|
+
const loadedMap = await loadCoValueOrFail(targetClient.node, map.id);
|
|
64
|
+
|
|
65
|
+
expect(loadedMap.core.getDependedOnCoValues()).toEqual(
|
|
66
|
+
new Set([group.core.id, sourceClient.accountID]),
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should track the account dependency when syncing a group extension", async () => {
|
|
71
|
+
const sourceClient = await setupTestAccount({
|
|
72
|
+
connected: true,
|
|
73
|
+
});
|
|
74
|
+
const targetClient = await setupTestAccount({
|
|
75
|
+
connected: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const group = sourceClient.node.createGroup();
|
|
79
|
+
const parentGroup = sourceClient.node.createGroup();
|
|
80
|
+
|
|
81
|
+
group.extend(parentGroup);
|
|
82
|
+
|
|
83
|
+
await group.core.waitForSync();
|
|
84
|
+
|
|
85
|
+
const loadedGroup = await loadCoValueOrFail(targetClient.node, group.id);
|
|
86
|
+
|
|
87
|
+
expect(loadedGroup.core.getDependedOnCoValues()).toEqual(
|
|
88
|
+
new Set([parentGroup.core.id, sourceClient.accountID]),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
@@ -539,7 +539,7 @@ describe("markErrored and isErroredInPeer", () => {
|
|
|
539
539
|
|
|
540
540
|
expect(coValue.isAvailable()).toBe(false);
|
|
541
541
|
|
|
542
|
-
const success = coValue.provideHeader(header
|
|
542
|
+
const success = coValue.provideHeader(header);
|
|
543
543
|
expect(success).toBe(true);
|
|
544
544
|
expect(coValue.isAvailable()).toBe(true);
|
|
545
545
|
});
|
|
@@ -559,7 +559,7 @@ describe("markErrored and isErroredInPeer", () => {
|
|
|
559
559
|
|
|
560
560
|
expect(coValue.isAvailable()).toBe(false);
|
|
561
561
|
|
|
562
|
-
const success = coValue.provideHeader(header
|
|
562
|
+
const success = coValue.provideHeader(header);
|
|
563
563
|
expect(success).toBe(false);
|
|
564
564
|
expect(coValue.isAvailable()).toBe(false);
|
|
565
565
|
});
|