cojson 0.13.25 → 0.13.28

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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/coValueCore/coValueCore.d.ts +4 -3
  4. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  5. package/dist/coValueCore/coValueCore.js +82 -66
  6. package/dist/coValueCore/coValueCore.js.map +1 -1
  7. package/dist/coValues/group.d.ts.map +1 -1
  8. package/dist/coValues/group.js +3 -1
  9. package/dist/coValues/group.js.map +1 -1
  10. package/dist/localNode.d.ts +1 -0
  11. package/dist/localNode.d.ts.map +1 -1
  12. package/dist/localNode.js +5 -2
  13. package/dist/localNode.js.map +1 -1
  14. package/dist/permissions.d.ts.map +1 -1
  15. package/dist/permissions.js +3 -1
  16. package/dist/permissions.js.map +1 -1
  17. package/dist/sync.d.ts +6 -6
  18. package/dist/sync.d.ts.map +1 -1
  19. package/dist/sync.js +100 -66
  20. package/dist/sync.js.map +1 -1
  21. package/dist/tests/SyncStateManager.test.js +1 -1
  22. package/dist/tests/SyncStateManager.test.js.map +1 -1
  23. package/dist/tests/group.removeMember.test.d.ts +2 -0
  24. package/dist/tests/group.removeMember.test.d.ts.map +1 -0
  25. package/dist/tests/group.removeMember.test.js +133 -0
  26. package/dist/tests/group.removeMember.test.js.map +1 -0
  27. package/dist/tests/sync.mesh.test.js +47 -3
  28. package/dist/tests/sync.mesh.test.js.map +1 -1
  29. package/dist/tests/sync.peerReconciliation.test.js +71 -2
  30. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  31. package/dist/tests/testUtils.d.ts.map +1 -1
  32. package/dist/tests/testUtils.js +1 -0
  33. package/dist/tests/testUtils.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/coValueCore/coValueCore.ts +111 -80
  36. package/src/coValues/group.ts +4 -1
  37. package/src/localNode.ts +6 -2
  38. package/src/permissions.ts +6 -1
  39. package/src/sync.ts +117 -71
  40. package/src/tests/SyncStateManager.test.ts +1 -1
  41. package/src/tests/group.removeMember.test.ts +255 -0
  42. package/src/tests/sync.mesh.test.ts +60 -2
  43. package/src/tests/sync.peerReconciliation.test.ts +90 -2
  44. package/src/tests/testUtils.ts +1 -0
@@ -109,7 +109,7 @@ export class CoValueCore {
109
109
  private readonly _decryptionCache: {
110
110
  [key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
111
111
  } = {};
112
- private _cachedDependentOn?: RawCoID[];
112
+ private _cachedDependentOn?: Set<RawCoID>;
113
113
  private counter: UpDownCounter;
114
114
 
115
115
  private constructor(
@@ -897,39 +897,57 @@ export class CoValueCore {
897
897
  ];
898
898
  }
899
899
 
900
- getDependedOnCoValues(): RawCoID[] {
900
+ getDependedOnCoValues(): Set<RawCoID> {
901
901
  if (this._cachedDependentOn) {
902
902
  return this._cachedDependentOn;
903
903
  } else {
904
- const dependentOn = this.getDependedOnCoValuesUncached();
904
+ if (!this.verified) {
905
+ return new Set();
906
+ }
907
+
908
+ const dependentOn = this.getDependedOnCoValuesFromHeaderAndSessions(
909
+ this.verified.header,
910
+ this.verified.sessions.keys(),
911
+ );
905
912
  this._cachedDependentOn = dependentOn;
906
913
  return dependentOn;
907
914
  }
908
915
  }
909
916
 
910
917
  /** @internal */
911
- getDependedOnCoValuesUncached(): RawCoID[] {
912
- if (!this.verified) {
913
- return [];
918
+ getDependedOnCoValuesFromHeaderAndSessions(
919
+ header: CoValueHeader,
920
+ sessions: Iterable<SessionID>,
921
+ ): Set<RawCoID> {
922
+ const deps = new Set<RawCoID>();
923
+
924
+ for (const session of sessions) {
925
+ const accountId = accountOrAgentIDfromSessionID(session);
926
+
927
+ if (isAccountID(accountId) && accountId !== this.id) {
928
+ deps.add(accountId);
929
+ }
914
930
  }
915
931
 
916
- return this.verified.header.ruleset.type === "group"
917
- ? getGroupDependentKeyList(expectGroup(this.getCurrentContent()).keys())
918
- : this.verified.header.ruleset.type === "ownedByGroup"
919
- ? [
920
- this.verified.header.ruleset.group,
921
- ...new Set(
922
- [...this.verified.sessions.keys()]
923
- .map((sessionID) =>
924
- accountOrAgentIDfromSessionID(sessionID as SessionID),
925
- )
926
- .filter(
927
- (session): session is RawAccountID =>
928
- isAccountID(session) && session !== this.id,
929
- ),
930
- ),
931
- ]
932
- : [];
932
+ if (header.ruleset.type === "group") {
933
+ if (isAccountID(header.ruleset.initialAdmin)) {
934
+ deps.add(header.ruleset.initialAdmin);
935
+ }
936
+
937
+ if (this.verified) {
938
+ for (const id of getGroupDependentKeyList(
939
+ expectGroup(this.getCurrentContent()).keys(),
940
+ )) {
941
+ deps.add(id);
942
+ }
943
+ }
944
+ }
945
+
946
+ if (header.ruleset.type === "ownedByGroup") {
947
+ deps.add(header.ruleset.group);
948
+ }
949
+
950
+ return deps;
933
951
  }
934
952
 
935
953
  waitForSync(options?: {
@@ -943,7 +961,11 @@ export class CoValueCore {
943
961
  return;
944
962
  }
945
963
 
946
- const peersToActuallyLoadFrom = [];
964
+ const peersToActuallyLoadFrom = {
965
+ storage: [] as PeerState[],
966
+ server: [] as PeerState[],
967
+ };
968
+
947
969
  for (const peer of peers) {
948
970
  const currentState = this.peers.get(peer.id);
949
971
 
@@ -959,78 +981,87 @@ export class CoValueCore {
959
981
  }
960
982
 
961
983
  if (currentState?.type === "unavailable") {
962
- if (peer.shouldRetryUnavailableCoValues()) {
984
+ if (peer.role === "server") {
985
+ peersToActuallyLoadFrom.server.push(peer);
963
986
  this.markPending(peer.id);
964
- peersToActuallyLoadFrom.push(peer);
965
987
  }
966
988
 
967
989
  continue;
968
990
  }
969
991
 
970
992
  if (!currentState || currentState?.type === "unknown") {
993
+ if (peer.role === "storage") {
994
+ peersToActuallyLoadFrom.storage.push(peer);
995
+ } else {
996
+ peersToActuallyLoadFrom.server.push(peer);
997
+ }
998
+
971
999
  this.markPending(peer.id);
972
- peersToActuallyLoadFrom.push(peer);
973
1000
  }
974
1001
  }
975
1002
 
976
- for (const peer of peersToActuallyLoadFrom) {
977
- if (peer.closed) {
978
- this.markNotFoundInPeer(peer.id);
979
- continue;
980
- }
1003
+ // Load from storage peers first, then from server peers
1004
+ if (peersToActuallyLoadFrom.storage.length > 0) {
1005
+ await Promise.all(
1006
+ peersToActuallyLoadFrom.storage.map((peer) =>
1007
+ this.internalLoadFromPeer(peer),
1008
+ ),
1009
+ );
1010
+ }
981
1011
 
982
- peer.pushOutgoingMessage({
983
- action: "load",
984
- ...this.knownState(),
985
- });
986
- peer.trackLoadRequestSent(this.id);
987
-
988
- /**
989
- * Use a very long timeout for storage peers, because under pressure
990
- * they may take a long time to consume the messages queue
991
- *
992
- * TODO: Track errors on storage and do not rely on timeout
993
- */
994
- const timeoutDuration =
995
- peer.role === "storage"
996
- ? CO_VALUE_LOADING_CONFIG.TIMEOUT * 10
997
- : CO_VALUE_LOADING_CONFIG.TIMEOUT;
998
-
999
- const waitingForPeer = new Promise<void>((resolve) => {
1000
- const markNotFound = () => {
1001
- if (this.peers.get(peer.id)?.type === "pending") {
1002
- logger.warn("Timeout waiting for peer to load coValue", {
1003
- id: this.id,
1004
- peerID: peer.id,
1005
- });
1006
- this.markNotFoundInPeer(peer.id);
1007
- }
1008
- };
1012
+ if (peersToActuallyLoadFrom.server.length > 0) {
1013
+ await Promise.all(
1014
+ peersToActuallyLoadFrom.server.map((peer) =>
1015
+ this.internalLoadFromPeer(peer),
1016
+ ),
1017
+ );
1018
+ }
1019
+ }
1009
1020
 
1010
- const timeout = setTimeout(markNotFound, timeoutDuration);
1011
- const removeCloseListener = peer.addCloseListener(markNotFound);
1021
+ internalLoadFromPeer(peer: PeerState) {
1022
+ if (peer.closed) {
1023
+ this.markNotFoundInPeer(peer.id);
1024
+ return;
1025
+ }
1012
1026
 
1013
- const listener = (state: CoValueCore) => {
1014
- const peerState = state.peers.get(peer.id);
1015
- if (
1016
- state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
1017
- peerState?.type === "available" ||
1018
- peerState?.type === "errored" ||
1019
- peerState?.type === "unavailable"
1020
- ) {
1021
- this.listeners.delete(listener);
1022
- removeCloseListener();
1023
- clearTimeout(timeout);
1024
- resolve();
1025
- }
1026
- };
1027
+ peer.pushOutgoingMessage({
1028
+ action: "load",
1029
+ ...this.knownState(),
1030
+ });
1031
+ peer.trackLoadRequestSent(this.id);
1027
1032
 
1028
- this.listeners.add(listener);
1029
- listener(this);
1030
- });
1033
+ return new Promise<void>((resolve) => {
1034
+ const markNotFound = () => {
1035
+ if (this.peers.get(peer.id)?.type === "pending") {
1036
+ logger.warn("Timeout waiting for peer to load coValue", {
1037
+ id: this.id,
1038
+ peerID: peer.id,
1039
+ });
1040
+ this.markNotFoundInPeer(peer.id);
1041
+ }
1042
+ };
1031
1043
 
1032
- await waitingForPeer;
1033
- }
1044
+ const timeout = setTimeout(markNotFound, CO_VALUE_LOADING_CONFIG.TIMEOUT);
1045
+ const removeCloseListener = peer.addCloseListener(markNotFound);
1046
+
1047
+ const listener = (state: CoValueCore) => {
1048
+ const peerState = state.peers.get(peer.id);
1049
+ if (
1050
+ state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
1051
+ peerState?.type === "available" ||
1052
+ peerState?.type === "errored" ||
1053
+ peerState?.type === "unavailable"
1054
+ ) {
1055
+ this.listeners.delete(listener);
1056
+ removeCloseListener();
1057
+ clearTimeout(timeout);
1058
+ resolve();
1059
+ }
1060
+ };
1061
+
1062
+ this.listeners.add(listener);
1063
+ listener(this);
1064
+ });
1034
1065
  }
1035
1066
  }
1036
1067
 
@@ -773,7 +773,10 @@ export class RawGroup<
773
773
  ) {
774
774
  const memberKey = typeof account === "string" ? account : account.id;
775
775
 
776
- this.rotateReadKey(memberKey);
776
+ if (this.myRole() === "admin") {
777
+ this.rotateReadKey(memberKey);
778
+ }
779
+
777
780
  this.set(memberKey, "revoked", "trusting");
778
781
  }
779
782
 
package/src/localNode.ts CHANGED
@@ -104,8 +104,12 @@ export class LocalNode {
104
104
  this.coValues.delete(id);
105
105
  }
106
106
 
107
+ getCurrentAccountOrAgentID(): RawAccountID | AgentID {
108
+ return accountOrAgentIDfromSessionID(this.currentSessionID);
109
+ }
110
+
107
111
  getCurrentAgent(): ControlledAccountOrAgent {
108
- const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
112
+ const accountOrAgent = this.getCurrentAccountOrAgentID();
109
113
  if (isAgentID(accountOrAgent)) {
110
114
  return new ControlledAgent(this.agentSecret, this.crypto);
111
115
  }
@@ -118,7 +122,7 @@ export class LocalNode {
118
122
  }
119
123
 
120
124
  expectCurrentAccountID(reason: string): RawAccountID {
121
- const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
125
+ const accountOrAgent = this.getCurrentAccountOrAgentID();
122
126
  if (isAgentID(accountOrAgent)) {
123
127
  throw new Error(
124
128
  "Current account is an agent, but expected an account: " + reason,
@@ -452,7 +452,12 @@ function determineValidTransactionsForGroup(
452
452
  change.key === transactor &&
453
453
  change.value === "admin";
454
454
 
455
- if (!isFirstSelfAppointment) {
455
+ const currentAccountId = coValue.node.getCurrentAccountOrAgentID();
456
+
457
+ const isSelfRevoke =
458
+ currentAccountId === change.key && change.value === "revoked";
459
+
460
+ if (!isFirstSelfAppointment && !isSelfRevoke) {
456
461
  if (memberState[transactor] === "admin") {
457
462
  if (
458
463
  memberState[affectedMember] === "admin" &&
package/src/sync.ts CHANGED
@@ -11,6 +11,8 @@ import { RawCoID, SessionID } from "./ids.js";
11
11
  import { LocalNode } from "./localNode.js";
12
12
  import { logger } from "./logger.js";
13
13
  import { CoValuePriority } from "./priority.js";
14
+ import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
15
+ import { isAccountID } from "./typeUtils/isAccountID.js";
14
16
 
15
17
  export type CoValueKnownState = {
16
18
  id: RawCoID;
@@ -211,9 +213,9 @@ export class SyncManager {
211
213
  return;
212
214
  }
213
215
 
214
- coValue
215
- .getDependedOnCoValues()
216
- .map((id) => this.sendNewContentIncludingDependencies(id, peer));
216
+ for (const dependency of coValue.getDependedOnCoValues()) {
217
+ this.sendNewContentIncludingDependencies(dependency, peer);
218
+ }
217
219
 
218
220
  const newContentPieces = coValue.verified.newContentSince(
219
221
  peer.optimisticKnownStates.get(id),
@@ -298,7 +300,7 @@ export class SyncManager {
298
300
  }
299
301
  }
300
302
 
301
- async addPeer(peer: Peer) {
303
+ addPeer(peer: Peer) {
302
304
  const prevPeer = this.peers[peer.id];
303
305
 
304
306
  if (prevPeer && !prevPeer.closed) {
@@ -379,65 +381,17 @@ export class SyncManager {
379
381
  peer.setKnownState(msg.id, knownStateIn(msg));
380
382
  const coValue = this.local.getCoValue(msg.id);
381
383
 
382
- if (
383
- coValue.loadingState === "unknown" ||
384
- coValue.loadingState === "unavailable"
385
- ) {
386
- const eligiblePeers = this.getServerAndStoragePeers(peer.id);
387
-
388
- if (eligiblePeers.length === 0) {
389
- // We don't have any eligible peers to load the coValue from
390
- // so we send a known state back to the sender to let it know
391
- // that the coValue is unavailable
392
- peer.trackToldKnownState(msg.id);
393
- this.trySendToPeer(peer, {
394
- action: "known",
395
- id: msg.id,
396
- header: false,
397
- sessions: {},
398
- });
399
-
400
- return;
401
- } else {
402
- // Syncronously updates the state loading is possible
403
- coValue
404
- .loadFromPeers(this.getServerAndStoragePeers(peer.id))
405
- .catch((e) => {
406
- logger.error("Error loading coValue in handleLoad", { err: e });
407
- });
408
- }
384
+ if (coValue.isAvailable()) {
385
+ this.sendNewContentIncludingDependencies(msg.id, peer);
386
+ return;
409
387
  }
410
388
 
411
- if (coValue.loadingState === "loading") {
412
- // We need to return from handleLoad immediately and wait for the CoValue to be loaded
413
- // in a new task, otherwise we might block further incoming content messages that would
414
- // resolve the CoValue as available. This can happen when we receive fresh
415
- // content from a client, but we are a server with our own upstream server(s)
416
- coValue
417
- .waitForAvailableOrUnavailable()
418
- .then(async (value) => {
419
- if (!value.isAvailable()) {
420
- peer.trackToldKnownState(msg.id);
421
- this.trySendToPeer(peer, {
422
- action: "known",
423
- id: msg.id,
424
- header: false,
425
- sessions: {},
426
- });
427
-
428
- return;
429
- }
389
+ const eligiblePeers = this.getServerAndStoragePeers(peer.id);
430
390
 
431
- this.sendNewContentIncludingDependencies(msg.id, peer);
432
- })
433
- .catch((e) => {
434
- logger.error("Error loading coValue in handleLoad loading state", {
435
- err: e,
436
- });
437
- });
438
- } else if (coValue.isAvailable()) {
439
- this.sendNewContentIncludingDependencies(msg.id, peer);
440
- } else {
391
+ if (eligiblePeers.length === 0) {
392
+ // We don't have any eligible peers to load the coValue from
393
+ // so we send a known state back to the sender to let it know
394
+ // that the coValue is unavailable
441
395
  peer.trackToldKnownState(msg.id);
442
396
  this.trySendToPeer(peer, {
443
397
  action: "known",
@@ -445,9 +399,41 @@ export class SyncManager {
445
399
  header: false,
446
400
  sessions: {},
447
401
  });
402
+
403
+ return;
448
404
  }
449
- }
450
405
 
406
+ coValue.loadFromPeers(eligiblePeers).catch((e) => {
407
+ logger.error("Error loading coValue in handleLoad", { err: e });
408
+ });
409
+
410
+ // We need to return from handleLoad immediately and wait for the CoValue to be loaded
411
+ // in a new task, otherwise we might block further incoming content messages that would
412
+ // resolve the CoValue as available. This can happen when we receive fresh
413
+ // content from a client, but we are a server with our own upstream server(s)
414
+ coValue
415
+ .waitForAvailableOrUnavailable()
416
+ .then((value) => {
417
+ if (!value.isAvailable()) {
418
+ peer.trackToldKnownState(msg.id);
419
+ this.trySendToPeer(peer, {
420
+ action: "known",
421
+ id: msg.id,
422
+ header: false,
423
+ sessions: {},
424
+ });
425
+
426
+ return;
427
+ }
428
+
429
+ this.sendNewContentIncludingDependencies(msg.id, peer);
430
+ })
431
+ .catch((e) => {
432
+ logger.error("Error loading coValue in handleLoad loading state", {
433
+ err: e,
434
+ });
435
+ });
436
+ }
451
437
  handleKnownState(msg: KnownStateMessage, peer: PeerState) {
452
438
  const coValue = this.local.getCoValue(msg.id);
453
439
 
@@ -494,6 +480,52 @@ export class SyncManager {
494
480
  return;
495
481
  }
496
482
 
483
+ let dependencyMissing = false;
484
+ const sessionIDs = Object.keys(msg.new) as SessionID[];
485
+ for (const dependency of coValue.getDependedOnCoValuesFromHeaderAndSessions(
486
+ msg.header,
487
+ sessionIDs,
488
+ )) {
489
+ const dependencyCoValue = this.local.getCoValue(dependency);
490
+
491
+ if (!dependencyCoValue.isAvailable()) {
492
+ if (peer.role !== "storage") {
493
+ this.trySendToPeer(peer, {
494
+ action: "load",
495
+ id: dependency,
496
+ header: false,
497
+ sessions: {},
498
+ });
499
+ }
500
+
501
+ dependencyMissing = true;
502
+ }
503
+ }
504
+
505
+ if (dependencyMissing) {
506
+ if (peer.role !== "storage") {
507
+ /**
508
+ * If we have missing dependencies, we send a known state message to the peer
509
+ * to let it know that we need a correction update.
510
+ *
511
+ * Sync-wise is sub-optimal, but it gives us correctness until
512
+ * https://github.com/garden-co/jazz/issues/1917 is implemented.
513
+ */
514
+ this.trySendToPeer(peer, {
515
+ action: "known",
516
+ isCorrection: true,
517
+ id: msg.id,
518
+ header: false,
519
+ sessions: {},
520
+ });
521
+ } else {
522
+ /** Cases of broken deps from storage are recovered by falling back to the server peers */
523
+ coValue.loadFromPeers(this.getServerAndStoragePeers(peer.id));
524
+ }
525
+
526
+ return;
527
+ }
528
+
497
529
  peer.updateHeader(msg.id, true);
498
530
  coValue.markAvailable(msg.header, peer.id);
499
531
  }
@@ -528,6 +560,18 @@ export class SyncManager {
528
560
  continue;
529
561
  }
530
562
 
563
+ const accountId = accountOrAgentIDfromSessionID(sessionID);
564
+
565
+ if (isAccountID(accountId)) {
566
+ const account = this.local.getCoValue(accountId);
567
+
568
+ if (!account.isAvailable()) {
569
+ account.loadFromPeers([peer]);
570
+ invalidStateAssumed = true;
571
+ continue;
572
+ }
573
+ }
574
+
531
575
  const result = coValue.tryAddTransactions(
532
576
  sessionID,
533
577
  newTransactions,
@@ -607,6 +651,8 @@ export class SyncManager {
607
651
  // Check if there is a inflight load operation and we
608
652
  // are waiting for other peers to send the load request
609
653
  if (state === "unknown" || state === undefined) {
654
+ // Sending a load message to the peer to get to know how much content is missing
655
+ // before sending the new content
610
656
  this.trySendToPeer(peer, {
611
657
  action: "load",
612
658
  ...coValue.knownState(),
@@ -645,7 +691,7 @@ export class SyncManager {
645
691
  this.requestedSyncs.add(coValue.id);
646
692
  }
647
693
 
648
- async syncCoValue(coValue: CoValueCore) {
694
+ syncCoValue(coValue: CoValueCore) {
649
695
  this.requestedSyncs.delete(coValue.id);
650
696
 
651
697
  for (const peer of this.peersInPriorityOrder()) {
@@ -668,21 +714,21 @@ export class SyncManager {
668
714
  }
669
715
  }
670
716
 
671
- async waitForSyncWithPeer(peerId: PeerID, id: RawCoID, timeout: number) {
717
+ waitForSyncWithPeer(peerId: PeerID, id: RawCoID, timeout: number) {
672
718
  const { syncState } = this;
673
719
  const currentSyncState = syncState.getCurrentSyncState(peerId, id);
674
720
 
675
721
  const isTheConditionAlreadyMet = currentSyncState.uploaded;
676
722
 
677
723
  if (isTheConditionAlreadyMet) {
678
- return true;
724
+ return;
679
725
  }
680
726
 
681
727
  const peerState = this.peers[peerId];
682
728
 
683
729
  // The peer has been closed, so it isn't possible to sync
684
730
  if (!peerState || peerState.closed) {
685
- return true;
731
+ return;
686
732
  }
687
733
 
688
734
  // The client isn't subscribed to the coValue, so we won't sync it
@@ -690,7 +736,7 @@ export class SyncManager {
690
736
  peerState.role === "client" &&
691
737
  !peerState.optimisticKnownStates.has(id)
692
738
  ) {
693
- return true;
739
+ return;
694
740
  }
695
741
 
696
742
  return new Promise((resolve, reject) => {
@@ -712,25 +758,25 @@ export class SyncManager {
712
758
  });
713
759
  }
714
760
 
715
- async waitForStorageSync(id: RawCoID, timeout = 30_000) {
761
+ waitForStorageSync(id: RawCoID, timeout = 30_000) {
716
762
  const peers = this.getPeers();
717
763
 
718
- await Promise.all(
764
+ return Promise.all(
719
765
  peers
720
766
  .filter((peer) => peer.role === "storage")
721
767
  .map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
722
768
  );
723
769
  }
724
770
 
725
- async waitForSync(id: RawCoID, timeout = 30_000) {
771
+ waitForSync(id: RawCoID, timeout = 30_000) {
726
772
  const peers = this.getPeers();
727
773
 
728
- await Promise.all(
774
+ return Promise.all(
729
775
  peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
730
776
  );
731
777
  }
732
778
 
733
- async waitForAllCoValuesSync(timeout = 60_000) {
779
+ waitForAllCoValuesSync(timeout = 60_000) {
734
780
  const coValues = this.local.allCoValues();
735
781
  const validCoValues = Array.from(coValues).filter(
736
782
  (coValue) =>
@@ -257,7 +257,7 @@ describe("SyncStateManager", () => {
257
257
  const group = client.node.createGroup();
258
258
  const map = group.createMap();
259
259
 
260
- await expect(map.core.waitForSync()).resolves.toBeUndefined();
260
+ await map.core.waitForSync();
261
261
  });
262
262
 
263
263
  test("should skip client peers that are not subscribed to the coValue", async () => {