cojson 0.13.12 → 0.13.14

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 (64) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/PeerState.d.ts +4 -6
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +30 -26
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/PriorityBasedMessageQueue.d.ts +2 -8
  8. package/dist/PriorityBasedMessageQueue.d.ts.map +1 -1
  9. package/dist/PriorityBasedMessageQueue.js +1 -17
  10. package/dist/PriorityBasedMessageQueue.js.map +1 -1
  11. package/dist/coValueCore.js +1 -1
  12. package/dist/coValueCore.js.map +1 -1
  13. package/dist/coValueState.d.ts.map +1 -1
  14. package/dist/coValueState.js +8 -19
  15. package/dist/coValueState.js.map +1 -1
  16. package/dist/localNode.d.ts.map +1 -1
  17. package/dist/localNode.js +2 -2
  18. package/dist/localNode.js.map +1 -1
  19. package/dist/streamUtils.d.ts.map +1 -1
  20. package/dist/streamUtils.js +1 -1
  21. package/dist/streamUtils.js.map +1 -1
  22. package/dist/sync.d.ts +11 -17
  23. package/dist/sync.d.ts.map +1 -1
  24. package/dist/sync.js +49 -91
  25. package/dist/sync.js.map +1 -1
  26. package/dist/tests/PeerState.test.js +27 -14
  27. package/dist/tests/PeerState.test.js.map +1 -1
  28. package/dist/tests/PriorityBasedMessageQueue.test.js +5 -33
  29. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -1
  30. package/dist/tests/SyncStateManager.test.js +5 -9
  31. package/dist/tests/SyncStateManager.test.js.map +1 -1
  32. package/dist/tests/sync.load.test.js +21 -17
  33. package/dist/tests/sync.load.test.js.map +1 -1
  34. package/dist/tests/sync.mesh.test.js +46 -18
  35. package/dist/tests/sync.mesh.test.js.map +1 -1
  36. package/dist/tests/sync.peerReconciliation.test.js +13 -15
  37. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  38. package/dist/tests/sync.storage.test.js +13 -9
  39. package/dist/tests/sync.storage.test.js.map +1 -1
  40. package/dist/tests/sync.test.js +16 -28
  41. package/dist/tests/sync.test.js.map +1 -1
  42. package/dist/tests/sync.upload.test.js +13 -13
  43. package/dist/tests/testUtils.d.ts +1 -0
  44. package/dist/tests/testUtils.d.ts.map +1 -1
  45. package/dist/tests/testUtils.js +1 -0
  46. package/dist/tests/testUtils.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/PeerState.ts +33 -37
  49. package/src/PriorityBasedMessageQueue.ts +2 -30
  50. package/src/coValueCore.ts +1 -1
  51. package/src/coValueState.ts +13 -21
  52. package/src/localNode.ts +4 -2
  53. package/src/streamUtils.ts +2 -2
  54. package/src/sync.ts +58 -103
  55. package/src/tests/PeerState.test.ts +28 -14
  56. package/src/tests/PriorityBasedMessageQueue.test.ts +5 -39
  57. package/src/tests/SyncStateManager.test.ts +4 -12
  58. package/src/tests/sync.load.test.ts +21 -17
  59. package/src/tests/sync.mesh.test.ts +46 -18
  60. package/src/tests/sync.peerReconciliation.test.ts +13 -25
  61. package/src/tests/sync.storage.test.ts +13 -9
  62. package/src/tests/sync.test.ts +16 -30
  63. package/src/tests/sync.upload.test.ts +13 -13
  64. package/src/tests/testUtils.ts +1 -0
package/src/PeerState.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import { PeerKnownStates, ReadonlyPeerKnownStates } from "./PeerKnownStates.js";
2
- import {
3
- PriorityBasedMessageQueue,
4
- QueueEntry,
5
- } from "./PriorityBasedMessageQueue.js";
6
- import { TryAddTransactionsError } from "./coValueCore.js";
2
+ import { PriorityBasedMessageQueue } from "./PriorityBasedMessageQueue.js";
7
3
  import { RawCoID, SessionID } from "./ids.js";
8
4
  import { logger } from "./logger.js";
9
5
  import { CO_VALUE_PRIORITY } from "./priority.js";
@@ -12,9 +8,6 @@ import { CoValueKnownState, Peer, SyncMessage } from "./sync.js";
12
8
  export class PeerState {
13
9
  private queue: PriorityBasedMessageQueue;
14
10
 
15
- incomingMessagesProcessingPromise: Promise<void> | undefined;
16
- nextPeer: Peer | undefined;
17
-
18
11
  constructor(
19
12
  private peer: Peer,
20
13
  knownStates: ReadonlyPeerKnownStates | undefined,
@@ -156,15 +149,26 @@ export class PeerState {
156
149
 
157
150
  this.processing = true;
158
151
 
159
- let entry: QueueEntry | undefined;
160
- while ((entry = this.queue.pull())) {
152
+ let msg: SyncMessage | undefined;
153
+ while ((msg = this.queue.pull())) {
154
+ if (this.closed) {
155
+ break;
156
+ }
157
+
161
158
  // Awaiting the push to send one message at a time
162
159
  // This way when the peer is "under pressure" we can enqueue all
163
160
  // the coming messages and organize them by priority
164
- await this.peer.outgoing
165
- .push(entry.msg)
166
- .then(entry.resolve)
167
- .catch(entry.reject);
161
+ try {
162
+ await this.peer.outgoing.push(msg);
163
+ } catch (e) {
164
+ logger.error("Error sending message", {
165
+ err: e,
166
+ action: msg.action,
167
+ id: msg.id,
168
+ peerId: this.id,
169
+ peerRole: this.role,
170
+ });
171
+ }
168
172
  }
169
173
 
170
174
  this.processing = false;
@@ -172,14 +176,16 @@ export class PeerState {
172
176
 
173
177
  pushOutgoingMessage(msg: SyncMessage) {
174
178
  if (this.closed) {
175
- return Promise.resolve();
179
+ return;
176
180
  }
177
181
 
178
- const promise = this.queue.push(msg);
182
+ this.queue.push(msg);
179
183
 
180
184
  void this.processQueue();
185
+ }
181
186
 
182
- return promise;
187
+ isProcessing() {
188
+ return this.processing;
183
189
  }
184
190
 
185
191
  get incoming() {
@@ -192,14 +198,6 @@ export class PeerState {
192
198
  return this.peer.incoming;
193
199
  }
194
200
 
195
- private closeQueue() {
196
- let entry: QueueEntry | undefined;
197
- while ((entry = this.queue.pull())) {
198
- // Using resolve here to avoid unnecessary noise in the logs
199
- entry.resolve();
200
- }
201
- }
202
-
203
201
  closeListeners = new Set<() => void>();
204
202
 
205
203
  addCloseListener(listener: () => void) {
@@ -228,40 +226,38 @@ export class PeerState {
228
226
  peerId: this.id,
229
227
  peerRole: this.role,
230
228
  });
231
- this.closeQueue();
232
229
  this.peer.outgoing.close();
233
230
  this.closed = true;
234
231
  this.emitClose();
235
232
  }
236
233
 
237
- async processIncomingMessages(callback: (msg: SyncMessage) => Promise<void>) {
234
+ async processIncomingMessages(callback: (msg: SyncMessage) => void) {
238
235
  if (this.closed) {
239
236
  throw new Error("Peer is closed");
240
237
  }
241
238
 
242
- if (this.incomingMessagesProcessingPromise) {
243
- throw new Error("Incoming messages processing already in progress");
244
- }
245
-
246
239
  const processIncomingMessages = async () => {
247
240
  for await (const msg of this.incoming) {
241
+ if (this.closed) {
242
+ return;
243
+ }
244
+
248
245
  if (msg === "Disconnected") {
249
- break;
246
+ return;
250
247
  }
248
+
251
249
  if (msg === "PingTimeout") {
252
250
  logger.error("Ping timeout from peer", {
253
251
  peerId: this.id,
254
252
  peerRole: this.role,
255
253
  });
256
- break;
254
+ return;
257
255
  }
258
256
 
259
- await callback(msg);
257
+ callback(msg);
260
258
  }
261
259
  };
262
260
 
263
- this.incomingMessagesProcessingPromise = processIncomingMessages();
264
-
265
- return this.incomingMessagesProcessingPromise;
261
+ return processIncomingMessages();
266
262
  }
267
263
  }
@@ -2,29 +2,6 @@ import { Counter, ValueType, metrics } from "@opentelemetry/api";
2
2
  import { CO_VALUE_PRIORITY, type CoValuePriority } from "./priority.js";
3
3
  import type { SyncMessage } from "./sync.js";
4
4
 
5
- function promiseWithResolvers<R>() {
6
- let resolve = (_: R) => {};
7
- let reject = (_: unknown) => {};
8
-
9
- const promise = new Promise<R>((_resolve, _reject) => {
10
- resolve = _resolve;
11
- reject = _reject;
12
- });
13
-
14
- return {
15
- promise,
16
- resolve,
17
- reject,
18
- };
19
- }
20
-
21
- export type QueueEntry = {
22
- msg: SyncMessage;
23
- promise: Promise<void>;
24
- resolve: () => void;
25
- reject: (_: unknown) => void;
26
- };
27
-
28
5
  /**
29
6
  * Since we have a fixed range of priority values (0-7) we can create a fixed array of queues.
30
7
  */
@@ -34,7 +11,7 @@ type Tuple<T, N extends number, A extends unknown[] = []> = A extends {
34
11
  ? A
35
12
  : Tuple<T, N, [...A, T]>;
36
13
 
37
- type QueueTuple = Tuple<LinkedList<QueueEntry>, 3>;
14
+ type QueueTuple = Tuple<LinkedList<SyncMessage>, 3>;
38
15
 
39
16
  type LinkedListNode<T> = {
40
17
  value: T;
@@ -164,14 +141,9 @@ export class PriorityBasedMessageQueue {
164
141
  }
165
142
 
166
143
  public push(msg: SyncMessage) {
167
- const { promise, resolve, reject } = promiseWithResolvers<void>();
168
- const entry: QueueEntry = { msg, promise, resolve, reject };
169
-
170
144
  const priority = "priority" in msg ? msg.priority : this.defaultPriority;
171
145
 
172
- this.getQueue(priority).push(entry);
173
-
174
- return promise;
146
+ this.getQueue(priority).push(msg);
175
147
  }
176
148
 
177
149
  public pull() {
@@ -488,7 +488,7 @@ export class CoValueCore {
488
488
 
489
489
  if (success) {
490
490
  this.node.syncManager.recordTransactionsSize([transaction], "local");
491
- void this.node.syncManager.syncCoValue(this);
491
+ void this.node.syncManager.requestCoValueSync(this);
492
492
  }
493
493
 
494
494
  return success;
@@ -44,21 +44,19 @@ export class CoValueState {
44
44
  get highLevelState() {
45
45
  if (this.core) {
46
46
  return "available";
47
- } else {
48
- if (this.peers.size === 0) {
49
- return "unknown";
50
- } else if (
51
- this.peers
52
- .values()
53
- .every((p) => p.type === "unavailable" || p.type === "errored")
54
- ) {
55
- return "unavailable";
56
- } else if (this.peers.values().some((p) => p.type === "pending")) {
47
+ } else if (this.peers.size === 0) {
48
+ return "unknown";
49
+ }
50
+
51
+ for (const peer of this.peers.values()) {
52
+ if (peer.type === "pending") {
57
53
  return "loading";
58
- } else {
54
+ } else if (peer.type === "unknown") {
59
55
  return "unknown";
60
56
  }
61
57
  }
58
+
59
+ return "unavailable";
62
60
  }
63
61
 
64
62
  isErroredInPeer(peerId: PeerID) {
@@ -143,16 +141,10 @@ export class CoValueState {
143
141
  continue;
144
142
  }
145
143
 
146
- peer
147
- .pushOutgoingMessage({
148
- action: "load",
149
- ...(this.core ? this.core.knownState() : emptyKnownState(this.id)),
150
- })
151
- .catch((err) => {
152
- logger.warn(`Failed to push load message to peer ${peer.id}`, {
153
- err,
154
- });
155
- });
144
+ peer.pushOutgoingMessage({
145
+ action: "load",
146
+ ...(this.core ? this.core.knownState() : emptyKnownState(this.id)),
147
+ });
156
148
 
157
149
  /**
158
150
  * Use a very long timeout for storage peers, because under pressure
package/src/localNode.ts CHANGED
@@ -140,7 +140,9 @@ export class LocalNode {
140
140
  function syncAllCoValuesAfterCreateAccount() {
141
141
  for (const coValueEntry of nodeWithAccount.coValuesStore.getValues()) {
142
142
  if (coValueEntry.isAvailable()) {
143
- void nodeWithAccount.syncManager.syncCoValue(coValueEntry.core);
143
+ void nodeWithAccount.syncManager.requestCoValueSync(
144
+ coValueEntry.core,
145
+ );
144
146
  }
145
147
  }
146
148
  }
@@ -248,7 +250,7 @@ export class LocalNode {
248
250
  const coValue = new CoValueCore(header, this);
249
251
  this.coValuesStore.internalMarkMagicallyAvailable(coValue.id, coValue);
250
252
 
251
- void this.syncManager.syncCoValue(coValue);
253
+ void this.syncManager.requestCoValueSync(coValue);
252
254
 
253
255
  return coValue;
254
256
  }
@@ -7,8 +7,8 @@ export function connectedPeers(
7
7
  peer2id: PeerID,
8
8
  {
9
9
  trace = false,
10
- peer1role = "peer",
11
- peer2role = "peer",
10
+ peer1role = "client",
11
+ peer2role = "client",
12
12
  crashOnClose = false,
13
13
  }: {
14
14
  trace?: boolean;
package/src/sync.ts CHANGED
@@ -78,7 +78,7 @@ export interface Peer {
78
78
  id: PeerID;
79
79
  incoming: IncomingSyncStream;
80
80
  outgoing: OutgoingSyncQueue;
81
- role: "peer" | "server" | "client" | "storage";
81
+ role: "server" | "client" | "storage";
82
82
  priority?: number;
83
83
  crashOnClose: boolean;
84
84
  deletePeerStateOnClose?: boolean;
@@ -112,11 +112,6 @@ export function combinedKnownStates(
112
112
  export class SyncManager {
113
113
  peers: { [key: PeerID]: PeerState } = {};
114
114
  local: LocalNode;
115
- requestedSyncs: {
116
- [id: RawCoID]:
117
- | { done: Promise<void>; nRequestsThisTick: number }
118
- | undefined;
119
- } = {};
120
115
 
121
116
  peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
122
117
  description: "Amount of connected peers",
@@ -162,7 +157,7 @@ export class SyncManager {
162
157
  );
163
158
  }
164
159
 
165
- async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
160
+ handleSyncMessage(msg: SyncMessage, peer: PeerState) {
166
161
  if (this.local.coValuesStore.get(msg.id).isErroredInPeer(peer.id)) {
167
162
  logger.warn(
168
163
  `Skipping message ${msg.action} on errored coValue ${msg.id} from peer ${peer.id}`,
@@ -183,18 +178,17 @@ export class SyncManager {
183
178
  // TODO: validate
184
179
  switch (msg.action) {
185
180
  case "load":
186
- return await this.handleLoad(msg, peer);
181
+ return this.handleLoad(msg, peer);
187
182
  case "known":
188
183
  if (msg.isCorrection) {
189
- return await this.handleCorrection(msg, peer);
184
+ return this.handleCorrection(msg, peer);
190
185
  } else {
191
- return await this.handleKnownState(msg, peer);
186
+ return this.handleKnownState(msg, peer);
192
187
  }
193
188
  case "content":
194
- // await new Promise<void>((resolve) => setTimeout(resolve, 0));
195
- return await this.handleNewContent(msg, peer);
189
+ return this.handleNewContent(msg, peer);
196
190
  case "done":
197
- return await this.handleUnsubscribe(msg);
191
+ return this.handleUnsubscribe(msg);
198
192
  default:
199
193
  throw new Error(
200
194
  `Unknown message type ${(msg as { action: "string" }).action}`,
@@ -202,14 +196,12 @@ export class SyncManager {
202
196
  }
203
197
  }
204
198
 
205
- async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
199
+ sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
206
200
  const coValue = this.local.expectCoValueLoaded(id);
207
201
 
208
- await Promise.all(
209
- coValue
210
- .getDependedOnCoValues()
211
- .map((id) => this.sendNewContentIncludingDependencies(id, peer)),
212
- );
202
+ coValue
203
+ .getDependedOnCoValues()
204
+ .map((id) => this.sendNewContentIncludingDependencies(id, peer));
213
205
 
214
206
  const newContentPieces = coValue.newContentSince(
215
207
  peer.optimisticKnownStates.get(id),
@@ -217,9 +209,7 @@ export class SyncManager {
217
209
 
218
210
  if (newContentPieces) {
219
211
  for (const piece of newContentPieces) {
220
- this.trySendToPeer(peer, piece).catch((e: unknown) => {
221
- logger.error("Error sending content piece", { err: e });
222
- });
212
+ this.trySendToPeer(peer, piece);
223
213
  }
224
214
 
225
215
  peer.toldKnownState.add(id);
@@ -228,15 +218,13 @@ export class SyncManager {
228
218
  this.trySendToPeer(peer, {
229
219
  action: "known",
230
220
  ...coValue.knownState(),
231
- }).catch((e: unknown) => {
232
- logger.error("Error sending known state", { err: e });
233
221
  });
234
222
 
235
223
  peer.toldKnownState.add(id);
236
224
  }
237
225
  }
238
226
 
239
- async startPeerReconciliation(peer: PeerState) {
227
+ startPeerReconciliation(peer: PeerState) {
240
228
  const coValuesOrderedByDependency: CoValueCore[] = [];
241
229
 
242
230
  const gathered = new Set<string>();
@@ -264,8 +252,12 @@ export class SyncManager {
264
252
  // If the coValue is unavailable and we never tried this peer
265
253
  // we try to load it from the peer
266
254
  if (!peer.toldKnownState.has(entry.id)) {
267
- await entry.loadFromPeers([peer]).catch((e: unknown) => {
268
- logger.error("Error sending load", { err: e });
255
+ peer.toldKnownState.add(entry.id);
256
+ this.trySendToPeer(peer, {
257
+ action: "load",
258
+ header: false,
259
+ id: entry.id,
260
+ sessions: {},
269
261
  });
270
262
  }
271
263
  } else {
@@ -293,33 +285,15 @@ export class SyncManager {
293
285
  this.trySendToPeer(peer, {
294
286
  action: "load",
295
287
  ...coValue.knownState(),
296
- }).catch((e: unknown) => {
297
- logger.error("Error sending load", { err: e });
298
288
  });
299
289
  }
300
290
  }
301
291
 
302
- nextPeer: Map<PeerID, Peer> = new Map();
303
-
304
292
  async addPeer(peer: Peer) {
305
293
  const prevPeer = this.peers[peer.id];
306
294
 
307
- if (prevPeer) {
308
- // Assign to nextPeer to check against race conditions
309
- prevPeer.nextPeer = peer;
310
-
311
- if (!prevPeer.closed) {
312
- prevPeer.gracefulShutdown();
313
- }
314
-
315
- // Wait for the previous peer to finish processing the incoming messages
316
- await prevPeer.incomingMessagesProcessingPromise?.catch((e) => {});
317
-
318
- // If another peer was added in the meantime, we close this peer
319
- if (prevPeer.nextPeer !== peer) {
320
- peer.outgoing.close();
321
- return;
322
- }
295
+ if (prevPeer && !prevPeer.closed) {
296
+ prevPeer.gracefulShutdown();
323
297
  }
324
298
 
325
299
  const peerState = new PeerState(peer, prevPeer?.knownStates);
@@ -338,8 +312,8 @@ export class SyncManager {
338
312
  }
339
313
 
340
314
  peerState
341
- .processIncomingMessages(async (msg) => {
342
- await this.handleSyncMessage(msg, peerState);
315
+ .processIncomingMessages((msg) => {
316
+ this.handleSyncMessage(msg, peerState);
343
317
  })
344
318
  .then(() => {
345
319
  if (peer.crashOnClose) {
@@ -386,7 +360,7 @@ export class SyncManager {
386
360
  * - The peer known state is stored as-is instead of being merged
387
361
  * - The load message always replies with a known state message
388
362
  */
389
- async handleLoad(msg: LoadMessage, peer: PeerState) {
363
+ handleLoad(msg: LoadMessage, peer: PeerState) {
390
364
  /**
391
365
  * We use the msg sessions as source of truth for the known states
392
366
  *
@@ -413,8 +387,6 @@ export class SyncManager {
413
387
  id: msg.id,
414
388
  header: false,
415
389
  sessions: {},
416
- }).catch((e) => {
417
- logger.error("Error sending known state back", { err: e });
418
390
  });
419
391
 
420
392
  return;
@@ -442,14 +414,12 @@ export class SyncManager {
442
414
  id: msg.id,
443
415
  header: false,
444
416
  sessions: {},
445
- }).catch((e) => {
446
- logger.error("Error sending known state back", { err: e });
447
417
  });
448
418
 
449
419
  return;
450
420
  }
451
421
 
452
- await this.sendNewContentIncludingDependencies(msg.id, peer);
422
+ this.sendNewContentIncludingDependencies(msg.id, peer);
453
423
  })
454
424
  .catch((e) => {
455
425
  logger.error("Error loading coValue in handleLoad loading state", {
@@ -457,7 +427,7 @@ export class SyncManager {
457
427
  });
458
428
  });
459
429
  } else if (entry.isAvailable()) {
460
- await this.sendNewContentIncludingDependencies(msg.id, peer);
430
+ this.sendNewContentIncludingDependencies(msg.id, peer);
461
431
  } else {
462
432
  this.trySendToPeer(peer, {
463
433
  action: "known",
@@ -468,7 +438,7 @@ export class SyncManager {
468
438
  }
469
439
  }
470
440
 
471
- async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
441
+ handleKnownState(msg: KnownStateMessage, peer: PeerState) {
472
442
  const entry = this.local.coValuesStore.get(msg.id);
473
443
 
474
444
  peer.combineWith(msg.id, knownStateIn(msg));
@@ -482,7 +452,7 @@ export class SyncManager {
482
452
  }
483
453
 
484
454
  if (entry.isAvailable()) {
485
- await this.sendNewContentIncludingDependencies(msg.id, peer);
455
+ this.sendNewContentIncludingDependencies(msg.id, peer);
486
456
  }
487
457
  }
488
458
 
@@ -499,7 +469,7 @@ export class SyncManager {
499
469
  }
500
470
  }
501
471
 
502
- async handleNewContent(msg: NewContentMessage, peer: PeerState) {
472
+ handleNewContent(msg: NewContentMessage, peer: PeerState) {
503
473
  const entry = this.local.coValuesStore.get(msg.id);
504
474
 
505
475
  let coValue: CoValueCore;
@@ -512,12 +482,6 @@ export class SyncManager {
512
482
  id: msg.id,
513
483
  header: false,
514
484
  sessions: {},
515
- }).catch((e) => {
516
- logger.error("Error sending known state correction", {
517
- peerId: peer.id,
518
- peerRole: peer.role,
519
- err: e,
520
- });
521
485
  });
522
486
  return;
523
487
  }
@@ -590,12 +554,6 @@ export class SyncManager {
590
554
  action: "known",
591
555
  isCorrection: true,
592
556
  ...coValue.knownState(),
593
- }).catch((e) => {
594
- logger.error("Error sending known state correction", {
595
- peerId: peer.id,
596
- peerRole: peer.role,
597
- err: e,
598
- });
599
557
  });
600
558
  peer.toldKnownState.add(msg.id);
601
559
  } else {
@@ -609,12 +567,6 @@ export class SyncManager {
609
567
  this.trySendToPeer(peer, {
610
568
  action: "known",
611
569
  ...coValue.knownState(),
612
- }).catch((e: unknown) => {
613
- logger.error("Error sending known state", {
614
- peerId: peer.id,
615
- peerRole: peer.role,
616
- err: e,
617
- });
618
570
  });
619
571
  peer.toldKnownState.add(msg.id);
620
572
  }
@@ -624,51 +576,54 @@ export class SyncManager {
624
576
  * response to the peers that are waiting for confirmation that a coValue is
625
577
  * fully synced
626
578
  */
627
- this.syncCoValue(coValue);
579
+ this.requestCoValueSync(coValue);
628
580
  }
629
581
 
630
- async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
582
+ handleCorrection(msg: KnownStateMessage, peer: PeerState) {
631
583
  peer.setKnownState(msg.id, knownStateIn(msg));
632
584
 
633
585
  return this.sendNewContentIncludingDependencies(msg.id, peer);
634
586
  }
635
587
 
636
- handleUnsubscribe(_msg: DoneMessage) {
637
- throw new Error("Method not implemented.");
638
- }
588
+ handleUnsubscribe(_msg: DoneMessage) {}
639
589
 
640
- async syncCoValue(coValue: CoValueCore) {
641
- if (this.requestedSyncs[coValue.id]) {
642
- this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
643
- return this.requestedSyncs[coValue.id]!.done;
590
+ requestedSyncs = new Map<RawCoID, Promise<void>>();
591
+
592
+ async requestCoValueSync(coValue: CoValueCore) {
593
+ const promise = this.requestedSyncs.get(coValue.id);
594
+
595
+ if (promise) {
596
+ return promise;
644
597
  } else {
645
- const done = new Promise<void>((resolve) => {
646
- queueMicrotask(async () => {
647
- delete this.requestedSyncs[coValue.id];
648
- await this.actuallySyncCoValue(coValue);
598
+ const promise = new Promise<void>((resolve) => {
599
+ queueMicrotask(() => {
600
+ this.requestedSyncs.delete(coValue.id);
601
+ this.syncCoValue(coValue);
649
602
  resolve();
650
603
  });
651
604
  });
652
- const entry = {
653
- done,
654
- nRequestsThisTick: 1,
655
- };
656
- this.requestedSyncs[coValue.id] = entry;
657
- return done;
605
+
606
+ this.requestedSyncs.set(coValue.id, promise);
607
+ return promise;
658
608
  }
659
609
  }
660
610
 
661
- async actuallySyncCoValue(coValue: CoValueCore) {
611
+ async syncCoValue(coValue: CoValueCore) {
612
+ const entry = this.local.coValuesStore.get(coValue.id);
613
+
662
614
  for (const peer of this.peersInPriorityOrder()) {
663
615
  if (peer.closed) continue;
664
- if (this.local.coValuesStore.get(coValue.id).isErroredInPeer(peer.id))
665
- continue;
616
+ if (entry.isErroredInPeer(peer.id)) continue;
666
617
 
667
- if (peer.optimisticKnownStates.has(coValue.id)) {
668
- await this.sendNewContentIncludingDependencies(coValue.id, peer);
669
- } else if (peer.isServerOrStoragePeer()) {
670
- await this.sendNewContentIncludingDependencies(coValue.id, peer);
618
+ // Only subscribed CoValues are synced to clients
619
+ if (
620
+ peer.role === "client" &&
621
+ !peer.optimisticKnownStates.has(coValue.id)
622
+ ) {
623
+ continue;
671
624
  }
625
+
626
+ this.sendNewContentIncludingDependencies(coValue.id, peer);
672
627
  }
673
628
 
674
629
  for (const peer of this.getPeers()) {