cojson 0.20.9 → 0.20.11

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 (179) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/OngoingStorageReconciliationTracker.d.ts +16 -0
  4. package/dist/OngoingStorageReconciliationTracker.d.ts.map +1 -0
  5. package/dist/OngoingStorageReconciliationTracker.js +75 -0
  6. package/dist/OngoingStorageReconciliationTracker.js.map +1 -0
  7. package/dist/PeerState.d.ts +2 -2
  8. package/dist/PeerState.d.ts.map +1 -1
  9. package/dist/PeerState.js +3 -3
  10. package/dist/PeerState.js.map +1 -1
  11. package/dist/StorageReconciliationAckTracker.d.ts +14 -0
  12. package/dist/StorageReconciliationAckTracker.d.ts.map +1 -0
  13. package/dist/StorageReconciliationAckTracker.js +72 -0
  14. package/dist/StorageReconciliationAckTracker.js.map +1 -0
  15. package/dist/SyncStateManager.js +2 -2
  16. package/dist/SyncStateManager.js.map +1 -1
  17. package/dist/coValueCore/coValueCore.d.ts +2 -1
  18. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  19. package/dist/coValueCore/coValueCore.js +43 -10
  20. package/dist/coValueCore/coValueCore.js.map +1 -1
  21. package/dist/coValues/coList.d.ts +2 -0
  22. package/dist/coValues/coList.d.ts.map +1 -1
  23. package/dist/coValues/coList.js +28 -0
  24. package/dist/coValues/coList.js.map +1 -1
  25. package/dist/coValues/group.d.ts +4 -1
  26. package/dist/coValues/group.d.ts.map +1 -1
  27. package/dist/coValues/group.js +15 -1
  28. package/dist/coValues/group.js.map +1 -1
  29. package/dist/config.d.ts +8 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +14 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/exports.d.ts +9 -1
  34. package/dist/exports.d.ts.map +1 -1
  35. package/dist/exports.js +5 -1
  36. package/dist/exports.js.map +1 -1
  37. package/dist/localNode.d.ts +7 -3
  38. package/dist/localNode.d.ts.map +1 -1
  39. package/dist/localNode.js +13 -5
  40. package/dist/localNode.js.map +1 -1
  41. package/dist/permissions.d.ts +1 -0
  42. package/dist/permissions.d.ts.map +1 -1
  43. package/dist/queue/LinkedList.d.ts +2 -0
  44. package/dist/queue/LinkedList.d.ts.map +1 -1
  45. package/dist/queue/LinkedList.js +7 -0
  46. package/dist/queue/LinkedList.js.map +1 -1
  47. package/dist/queue/OutgoingLoadQueue.d.ts +4 -1
  48. package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -1
  49. package/dist/queue/OutgoingLoadQueue.js +41 -13
  50. package/dist/queue/OutgoingLoadQueue.js.map +1 -1
  51. package/dist/queue/PriorityBasedMessageQueue.d.ts +1 -0
  52. package/dist/queue/PriorityBasedMessageQueue.d.ts.map +1 -1
  53. package/dist/queue/PriorityBasedMessageQueue.js +11 -1
  54. package/dist/queue/PriorityBasedMessageQueue.js.map +1 -1
  55. package/dist/storage/knownState.d.ts +2 -0
  56. package/dist/storage/knownState.d.ts.map +1 -1
  57. package/dist/storage/knownState.js +11 -0
  58. package/dist/storage/knownState.js.map +1 -1
  59. package/dist/storage/sqlite/client.d.ts +10 -1
  60. package/dist/storage/sqlite/client.d.ts.map +1 -1
  61. package/dist/storage/sqlite/client.js +84 -0
  62. package/dist/storage/sqlite/client.js.map +1 -1
  63. package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
  64. package/dist/storage/sqlite/sqliteMigrations.js +11 -0
  65. package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
  66. package/dist/storage/sqliteAsync/client.d.ts +10 -1
  67. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  68. package/dist/storage/sqliteAsync/client.js +86 -0
  69. package/dist/storage/sqliteAsync/client.js.map +1 -1
  70. package/dist/storage/storageAsync.d.ts +9 -2
  71. package/dist/storage/storageAsync.d.ts.map +1 -1
  72. package/dist/storage/storageAsync.js +19 -0
  73. package/dist/storage/storageAsync.js.map +1 -1
  74. package/dist/storage/storageSync.d.ts +9 -2
  75. package/dist/storage/storageSync.d.ts.map +1 -1
  76. package/dist/storage/storageSync.js +20 -13
  77. package/dist/storage/storageSync.js.map +1 -1
  78. package/dist/storage/types.d.ts +64 -0
  79. package/dist/storage/types.d.ts.map +1 -1
  80. package/dist/storage/types.js.map +1 -1
  81. package/dist/sync.d.ts +53 -2
  82. package/dist/sync.d.ts.map +1 -1
  83. package/dist/sync.js +300 -44
  84. package/dist/sync.js.map +1 -1
  85. package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts +2 -0
  86. package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts.map +1 -0
  87. package/dist/tests/OngoingStorageReconciliationTracker.test.js +60 -0
  88. package/dist/tests/OngoingStorageReconciliationTracker.test.js.map +1 -0
  89. package/dist/tests/OutgoingLoadQueue.test.js +137 -39
  90. package/dist/tests/OutgoingLoadQueue.test.js.map +1 -1
  91. package/dist/tests/SQLiteClientAsync.test.js +1 -1
  92. package/dist/tests/SQLiteClientAsync.test.js.map +1 -1
  93. package/dist/tests/StorageApiAsync.test.js +138 -0
  94. package/dist/tests/StorageApiAsync.test.js.map +1 -1
  95. package/dist/tests/StorageApiSync.test.js +154 -0
  96. package/dist/tests/StorageApiSync.test.js.map +1 -1
  97. package/dist/tests/StorageReconciliationAckTracker.test.d.ts +2 -0
  98. package/dist/tests/StorageReconciliationAckTracker.test.d.ts.map +1 -0
  99. package/dist/tests/StorageReconciliationAckTracker.test.js +74 -0
  100. package/dist/tests/StorageReconciliationAckTracker.test.js.map +1 -0
  101. package/dist/tests/SyncStateManager.test.js +18 -0
  102. package/dist/tests/SyncStateManager.test.js.map +1 -1
  103. package/dist/tests/coList.test.js +112 -1
  104. package/dist/tests/coList.test.js.map +1 -1
  105. package/dist/tests/coValueCore.loadFromStorage.test.js +36 -0
  106. package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
  107. package/dist/tests/group.test.js +44 -0
  108. package/dist/tests/group.test.js.map +1 -1
  109. package/dist/tests/knownState.lazyLoading.test.js +6 -0
  110. package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
  111. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  112. package/dist/tests/messagesTestUtils.js +4 -0
  113. package/dist/tests/messagesTestUtils.js.map +1 -1
  114. package/dist/tests/sync.concurrentLoad.test.js +333 -1
  115. package/dist/tests/sync.concurrentLoad.test.js.map +1 -1
  116. package/dist/tests/sync.garbageCollection.test.js +4 -0
  117. package/dist/tests/sync.garbageCollection.test.js.map +1 -1
  118. package/dist/tests/sync.load.test.js +19 -0
  119. package/dist/tests/sync.load.test.js.map +1 -1
  120. package/dist/tests/sync.mesh.test.js +1 -0
  121. package/dist/tests/sync.mesh.test.js.map +1 -1
  122. package/dist/tests/sync.multipleServers.test.js +41 -3
  123. package/dist/tests/sync.multipleServers.test.js.map +1 -1
  124. package/dist/tests/sync.storage.test.js +2 -0
  125. package/dist/tests/sync.storage.test.js.map +1 -1
  126. package/dist/tests/sync.storageAsync.test.js +1 -0
  127. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  128. package/dist/tests/sync.storageReconciliation.test.d.ts +2 -0
  129. package/dist/tests/sync.storageReconciliation.test.d.ts.map +1 -0
  130. package/dist/tests/sync.storageReconciliation.test.js +502 -0
  131. package/dist/tests/sync.storageReconciliation.test.js.map +1 -0
  132. package/dist/tests/testUtils.d.ts +1 -0
  133. package/dist/tests/testUtils.d.ts.map +1 -1
  134. package/dist/tests/testUtils.js +3 -2
  135. package/dist/tests/testUtils.js.map +1 -1
  136. package/package.json +4 -4
  137. package/src/OngoingStorageReconciliationTracker.ts +97 -0
  138. package/src/PeerState.ts +10 -3
  139. package/src/StorageReconciliationAckTracker.ts +83 -0
  140. package/src/SyncStateManager.ts +3 -3
  141. package/src/coValueCore/coValueCore.ts +47 -16
  142. package/src/coValues/coList.ts +23 -0
  143. package/src/coValues/group.ts +18 -0
  144. package/src/config.ts +18 -0
  145. package/src/exports.ts +8 -0
  146. package/src/localNode.ts +18 -0
  147. package/src/permissions.ts +1 -1
  148. package/src/queue/LinkedList.ts +10 -0
  149. package/src/queue/OutgoingLoadQueue.ts +57 -15
  150. package/src/queue/PriorityBasedMessageQueue.ts +15 -1
  151. package/src/storage/knownState.ts +14 -0
  152. package/src/storage/sqlite/client.ts +128 -0
  153. package/src/storage/sqlite/sqliteMigrations.ts +11 -0
  154. package/src/storage/sqliteAsync/client.ts +139 -0
  155. package/src/storage/storageAsync.ts +37 -0
  156. package/src/storage/storageSync.ts +41 -16
  157. package/src/storage/types.ts +110 -0
  158. package/src/sync.ts +359 -14
  159. package/src/tests/OngoingStorageReconciliationTracker.test.ts +85 -0
  160. package/src/tests/OutgoingLoadQueue.test.ts +226 -59
  161. package/src/tests/SQLiteClientAsync.test.ts +1 -1
  162. package/src/tests/StorageApiAsync.test.ts +161 -1
  163. package/src/tests/StorageApiSync.test.ts +176 -0
  164. package/src/tests/StorageReconciliationAckTracker.test.ts +99 -0
  165. package/src/tests/SyncStateManager.test.ts +25 -0
  166. package/src/tests/coList.test.ts +138 -0
  167. package/src/tests/coValueCore.loadFromStorage.test.ts +72 -1
  168. package/src/tests/group.test.ts +87 -0
  169. package/src/tests/knownState.lazyLoading.test.ts +36 -1
  170. package/src/tests/messagesTestUtils.ts +4 -0
  171. package/src/tests/sync.concurrentLoad.test.ts +491 -0
  172. package/src/tests/sync.garbageCollection.test.ts +4 -0
  173. package/src/tests/sync.load.test.ts +26 -0
  174. package/src/tests/sync.mesh.test.ts +1 -0
  175. package/src/tests/sync.multipleServers.test.ts +60 -2
  176. package/src/tests/sync.storage.test.ts +2 -0
  177. package/src/tests/sync.storageAsync.test.ts +1 -0
  178. package/src/tests/sync.storageReconciliation.test.ts +696 -0
  179. package/src/tests/testUtils.ts +10 -1
package/dist/sync.d.ts CHANGED
@@ -9,7 +9,7 @@ import { CoValuePriority } from "./priority.js";
9
9
  import { IncomingMessagesQueue } from "./queue/IncomingMessagesQueue.js";
10
10
  import { CoValueKnownState, KnownStateSessions } from "./knownState.js";
11
11
  import { StorageAPI } from "./storage/index.js";
12
- export type SyncMessage = LoadMessage | KnownStateMessage | NewContentMessage | DoneMessage;
12
+ export type SyncMessage = LoadMessage | KnownStateMessage | NewContentMessage | DoneMessage | ReconcileMessage | ReconcileAckMessage;
13
13
  export type LoadMessage = {
14
14
  action: "load";
15
15
  } & CoValueKnownState;
@@ -37,6 +37,16 @@ export type DoneMessage = {
37
37
  action: "done";
38
38
  id: RawCoID;
39
39
  };
40
+ export type ReconcileBatchID = string;
41
+ export type ReconcileMessage = {
42
+ action: "reconcile";
43
+ id: ReconcileBatchID;
44
+ values: [coValue: RawCoID, sessionsHash: string][];
45
+ };
46
+ export type ReconcileAckMessage = {
47
+ action: "reconcile-ack";
48
+ id: ReconcileBatchID;
49
+ };
40
50
  /**
41
51
  * Determines when network sync is enabled.
42
52
  * - "always": sync is enabled for both Anonymous Authentication and Authenticated Account
@@ -66,14 +76,31 @@ export interface Peer {
66
76
  persistent?: boolean;
67
77
  }
68
78
  export type ServerPeerSelector = (id: RawCoID, serverPeers: PeerState[]) => PeerState[];
79
+ /**
80
+ * Manages the sync of coValues between peers.
81
+ * It is responsible for sending, receiving and processing sync messages.
82
+ * For more details on how the sync protocol works, see the sync protocol documentation:
83
+ * {@link docs/sync-protocol.md}
84
+ */
69
85
  export declare class SyncManager {
86
+ #private;
70
87
  peers: {
71
88
  [key: PeerID]: PeerState;
72
89
  };
73
90
  local: LocalNode;
91
+ /**
92
+ * Tracks pending reconcile acks from the server.
93
+ */
94
+ private reconciliationAckTracker;
95
+ /**
96
+ * Tracks ongoing storage reconciliation batches in a server.
97
+ */
98
+ private ongoingStorageReconciliationTracker;
99
+ get pendingReconciliationAck(): Map<string, number>;
74
100
  private skipVerify;
75
101
  private _ignoreUnknownCoValuesFromServers;
76
102
  ignoreUnknownCoValuesFromServers(): void;
103
+ fullStorageReconciliationEnabled: boolean;
77
104
  peersCounter: import("@opentelemetry/api").UpDownCounter<import("@opentelemetry/api").Attributes>;
78
105
  private transactionsSizeHistogram;
79
106
  serverPeerSelector?: ServerPeerSelector;
@@ -86,9 +113,29 @@ export declare class SyncManager {
86
113
  getServerPeers(id: RawCoID, excludePeerId?: PeerID): PeerState[];
87
114
  getPersistentServerPeers(id: RawCoID): PeerState[];
88
115
  handleSyncMessage(msg: SyncMessage, peer: PeerState): void;
89
- sendNewContent(id: RawCoID, peer: PeerState, seen?: Set<RawCoID>): void;
116
+ sendNewContent(id: RawCoID, peer: PeerState, forceKnownReplyOnNoDelta?: boolean): void;
117
+ /**
118
+ * Reconciles all in-memory CoValues with all persistent server peers
119
+ */
90
120
  reconcileServerPeers(): void;
121
+ /**
122
+ * Ensures all CoValues in storage are synced to the given server peer.
123
+ * Sends "reconcile" message(s) with [coValueId, sessionsHash] for each CoValue.
124
+ * Server responds with "known" only where it is missing the CoValue or has different sessions,
125
+ * so that client can send missing content.
126
+ * Processes CoValues in batches of RECONCILIATION_BATCH_SIZE.
127
+ * @param peer - The server peer to reconcile with.
128
+ * @param initialOffset - Offset to start from (for resuming after interrupt). Default 0.
129
+ * @param onComplete - Called when reconciliation is fully complete (all batches sent and acked).
130
+ */
131
+ startStorageReconciliation(peer: PeerState, initialOffset?: number, onComplete?: () => void): void;
132
+ private buildStorageReconciliationEntries;
133
+ private maybeStartStorageReconciliationForPeer;
91
134
  resumeUnsyncedCoValues(): Promise<void>;
135
+ /**
136
+ * Reconciles all in-memory CoValues with the given peer.
137
+ * Creates a subscription for each CoValue that is not already subscribed to.
138
+ */
92
139
  startPeerReconciliation(peer: PeerState): void;
93
140
  messagesQueue: IncomingMessagesQueue;
94
141
  private processing;
@@ -139,9 +186,13 @@ export declare class SyncManager {
139
186
  */
140
187
  private requestFullContent;
141
188
  handleKnownState(msg: KnownStateMessage, peer: PeerState): void;
189
+ handleReconcile(msg: ReconcileMessage, peer: PeerState): void;
190
+ handleReconcileAck(msg: ReconcileAckMessage, peer: PeerState): void;
191
+ private hashKnownStateSessions;
142
192
  recordTransactionsSize(newTransactions: Transaction[], source: string): void;
143
193
  handleNewContent(msg: NewContentMessage, from: PeerState | "storage" | "import"): void;
144
194
  handleCorrection(msg: KnownStateMessage, peer: PeerState): void;
195
+ private maybeMarkCoValueAsReconciled;
145
196
  private syncQueue;
146
197
  syncLocalTransaction: (coValue: import("./coValueCore/verifiedState.js").VerifiedState, knownStateBefore: CoValueKnownState) => void;
147
198
  trackDirtyCoValues: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
package/dist/sync.js CHANGED
@@ -1,24 +1,51 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _SyncManager_instances, _SyncManager_sendNewContent;
7
+ import { base58 } from "@scure/base";
1
8
  import { md5 } from "@noble/hashes/legacy";
2
9
  import { ValueType, metrics } from "@opentelemetry/api";
3
10
  import { PeerState } from "./PeerState.js";
4
11
  import { SyncStateManager } from "./SyncStateManager.js";
5
12
  import { UnsyncedCoValuesTracker } from "./UnsyncedCoValuesTracker.js";
6
- import { SYNC_SCHEDULER_CONFIG } from "./config.js";
13
+ import { STORAGE_RECONCILIATION_CONFIG, SYNC_SCHEDULER_CONFIG, } from "./config.js";
7
14
  import { getContenDebugInfo, getNewTransactionsFromContentMessage, getSessionEntriesFromContentMessage, getTransactionSize, knownStateFromContent, } from "./coValueContentMessage.js";
8
15
  import { isDeleteSessionID, isRawCoID } from "./ids.js";
9
16
  import { logger } from "./logger.js";
10
17
  import { IncomingMessagesQueue } from "./queue/IncomingMessagesQueue.js";
11
18
  import { LocalTransactionsSyncQueue } from "./queue/LocalTransactionsSyncQueue.js";
19
+ import { OngoingStorageReconciliationTracker } from "./OngoingStorageReconciliationTracker.js";
20
+ import { StorageReconciliationServerAckTracker } from "./StorageReconciliationAckTracker.js";
12
21
  import { knownStateFrom, peerHasAllContent, } from "./knownState.js";
13
22
  function isPersistentServerPeer(peer) {
14
23
  return peer.role === "server" && (peer.persistent ?? false);
15
24
  }
25
+ /**
26
+ * Manages the sync of coValues between peers.
27
+ * It is responsible for sending, receiving and processing sync messages.
28
+ * For more details on how the sync protocol works, see the sync protocol documentation:
29
+ * {@link docs/sync-protocol.md}
30
+ */
16
31
  export class SyncManager {
32
+ get pendingReconciliationAck() {
33
+ return this.reconciliationAckTracker.pendingReconciliationAck;
34
+ }
17
35
  ignoreUnknownCoValuesFromServers() {
18
36
  this._ignoreUnknownCoValuesFromServers = true;
19
37
  }
20
38
  constructor(local) {
39
+ _SyncManager_instances.add(this);
21
40
  this.peers = {};
41
+ /**
42
+ * Tracks pending reconcile acks from the server.
43
+ */
44
+ this.reconciliationAckTracker = new StorageReconciliationServerAckTracker();
45
+ /**
46
+ * Tracks ongoing storage reconciliation batches in a server.
47
+ */
48
+ this.ongoingStorageReconciliationTracker = new OngoingStorageReconciliationTracker();
22
49
  // When true, transactions will not be verified.
23
50
  // This is useful when syncing only for storage purposes, with the expectation that
24
51
  // the transactions have already been verified by the [trusted] peer that sent them.
@@ -26,6 +53,7 @@ export class SyncManager {
26
53
  // When true, coValues that arrive from server peers will be ignored if they had not
27
54
  // explicitly been requested via a load message.
28
55
  this._ignoreUnknownCoValuesFromServers = false;
56
+ this.fullStorageReconciliationEnabled = false;
29
57
  this.peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
30
58
  description: "Amount of connected peers",
31
59
  valueType: ValueType.INT,
@@ -66,6 +94,14 @@ export class SyncManager {
66
94
  return this.getServerPeers(id).filter((peer) => peer.persistent);
67
95
  }
68
96
  handleSyncMessage(msg, peer) {
97
+ if (msg.action === "reconcile") {
98
+ this.handleReconcile(msg, peer);
99
+ return;
100
+ }
101
+ if (msg.action === "reconcile-ack") {
102
+ this.handleReconcileAck(msg, peer);
103
+ return;
104
+ }
69
105
  if (!isRawCoID(msg.id)) {
70
106
  const errorType = msg.id ? "invalid" : "undefined";
71
107
  logger.warn(`Received sync message with ${errorType} id`, {
@@ -109,48 +145,136 @@ export class SyncManager {
109
145
  throw new Error(`Unknown message type ${msg.action}`);
110
146
  }
111
147
  }
112
- sendNewContent(id, peer, seen = new Set()) {
113
- if (seen.has(id)) {
114
- return;
148
+ sendNewContent(id, peer, forceKnownReplyOnNoDelta = false) {
149
+ __classPrivateFieldGet(this, _SyncManager_instances, "m", _SyncManager_sendNewContent).call(this, id, peer, new Set(), forceKnownReplyOnNoDelta);
150
+ }
151
+ /**
152
+ * Reconciles all in-memory CoValues with all persistent server peers
153
+ */
154
+ reconcileServerPeers() {
155
+ const serverPeers = Object.values(this.peers).filter(isPersistentServerPeer);
156
+ for (const peer of serverPeers) {
157
+ this.startPeerReconciliation(peer);
115
158
  }
116
- seen.add(id);
117
- const coValue = this.local.getCoValue(id);
118
- if (!coValue.isAvailable()) {
159
+ }
160
+ /**
161
+ * Ensures all CoValues in storage are synced to the given server peer.
162
+ * Sends "reconcile" message(s) with [coValueId, sessionsHash] for each CoValue.
163
+ * Server responds with "known" only where it is missing the CoValue or has different sessions,
164
+ * so that client can send missing content.
165
+ * Processes CoValues in batches of RECONCILIATION_BATCH_SIZE.
166
+ * @param peer - The server peer to reconcile with.
167
+ * @param initialOffset - Offset to start from (for resuming after interrupt). Default 0.
168
+ * @param onComplete - Called when reconciliation is fully complete (all batches sent and acked).
169
+ */
170
+ startStorageReconciliation(peer, initialOffset, onComplete) {
171
+ if (!this.local.storage)
119
172
  return;
120
- }
121
- const includeDependencies = peer.role !== "server";
122
- if (includeDependencies) {
123
- for (const dependency of coValue.getDependedOnCoValues()) {
124
- this.sendNewContent(dependency, peer, seen);
125
- }
126
- }
127
- const newContentPieces = coValue.newContentSince(peer.getOptimisticKnownState(id));
128
- if (newContentPieces) {
129
- for (const piece of newContentPieces) {
130
- this.trySendToPeer(peer, piece);
131
- }
132
- peer.combineOptimisticWith(id, coValue.knownState());
133
- }
134
- else if (!peer.toldKnownState.has(id)) {
135
- if (coValue.isDeleted) {
136
- // This way we make the peer believe that we've always ingested all the content they sent, even though we skipped it because the coValue is deleted
137
- this.trySendToPeer(peer, coValue.stopSyncingKnownStateMessage(peer.getKnownState(id)));
138
- }
139
- else {
173
+ if (!isPersistentServerPeer(peer))
174
+ return;
175
+ const startOffset = initialOffset ?? 0;
176
+ const batchSize = STORAGE_RECONCILIATION_CONFIG.BATCH_SIZE;
177
+ const storage = this.local.storage;
178
+ storage.getCoValueCount((totalCoValueCount) => {
179
+ const sendReconcileMessage = (batchId, entries, offset) => {
180
+ if (entries.length === 0)
181
+ return;
182
+ this.reconciliationAckTracker.trackBatch(batchId, peer.id, offset + batchSize);
140
183
  this.trySendToPeer(peer, {
141
- action: "known",
142
- ...coValue.knownStateWithStreaming(),
184
+ action: "reconcile",
185
+ id: batchId,
186
+ values: entries,
143
187
  });
144
- }
145
- }
146
- peer.trackToldKnownState(id);
188
+ };
189
+ const triggerNextBatch = (lastBatchLength, offset) => {
190
+ // This value becomes false when the last covalueid batch picked from the storage
191
+ // is smaller than the batch size.
192
+ if (lastBatchLength === batchSize) {
193
+ logger.info("Reconciled CoValues in storage", {
194
+ peerId: peer.id,
195
+ completed: offset + batchSize,
196
+ total: totalCoValueCount,
197
+ });
198
+ processStorageBatch(offset + batchSize);
199
+ }
200
+ else {
201
+ // Note: `completed` can be higher than `total` if CoValues were added
202
+ // after the reconciliation started
203
+ logger.info("Storage reconciliation complete", {
204
+ peerId: peer.id,
205
+ startOffset,
206
+ completed: offset + lastBatchLength,
207
+ total: totalCoValueCount,
208
+ });
209
+ onComplete?.();
210
+ }
211
+ };
212
+ const processStorageBatch = (offset) => {
213
+ storage.getCoValueIDs(batchSize, offset, (batch) => {
214
+ this.buildStorageReconciliationEntries(batch, (entries) => {
215
+ if (entries.length === 0) {
216
+ triggerNextBatch(batch.length, offset);
217
+ return;
218
+ }
219
+ const batchId = base58.encode(this.local.crypto.randomBytes(12));
220
+ sendReconcileMessage(batchId, entries, offset);
221
+ this.reconciliationAckTracker.waitForAck(batchId, peer, () => {
222
+ triggerNextBatch(batch.length, offset);
223
+ });
224
+ });
225
+ });
226
+ };
227
+ logger.info("Starting storage reconciliation", {
228
+ peerId: peer.id,
229
+ startOffset,
230
+ total: totalCoValueCount,
231
+ });
232
+ processStorageBatch(startOffset);
233
+ });
147
234
  }
148
- reconcileServerPeers() {
149
- const serverPeers = Object.values(this.peers).filter((peer) => peer.role === "server");
150
- for (const peer of serverPeers) {
151
- this.startPeerReconciliation(peer);
235
+ buildStorageReconciliationEntries(batch, callback) {
236
+ const storage = this.local.storage;
237
+ if (!storage) {
238
+ callback([]);
239
+ return;
240
+ }
241
+ const pending = batch.filter(({ id }) => !this.local.isCoValueInMemory(id));
242
+ if (pending.length === 0) {
243
+ callback([]);
244
+ return;
245
+ }
246
+ let done = 0;
247
+ const entries = [];
248
+ for (const coValue of pending) {
249
+ storage.loadKnownState(coValue.id, (storageKnownState) => {
250
+ if (storageKnownState) {
251
+ entries.push([
252
+ coValue.id,
253
+ this.hashKnownStateSessions(storageKnownState.sessions),
254
+ ]);
255
+ }
256
+ done += 1;
257
+ if (done === pending.length) {
258
+ callback(entries);
259
+ }
260
+ });
152
261
  }
153
262
  }
263
+ maybeStartStorageReconciliationForPeer(peer) {
264
+ if (!this.fullStorageReconciliationEnabled)
265
+ return;
266
+ if (!this.local.storage)
267
+ return;
268
+ const sessionId = this.local.currentSessionID;
269
+ this.local.storage.tryAcquireStorageReconciliationLock(sessionId, peer.id, (result) => {
270
+ if (!result.acquired)
271
+ return;
272
+ const lastProcessedOffset = result.lastProcessedOffset;
273
+ this.startStorageReconciliation(peer, lastProcessedOffset, () => {
274
+ this.local.storage?.releaseStorageReconciliationLock(sessionId, peer.id);
275
+ });
276
+ });
277
+ }
154
278
  async resumeUnsyncedCoValues() {
155
279
  if (!this.local.storage) {
156
280
  // No storage available, skip resumption
@@ -207,12 +331,18 @@ export class SyncManager {
207
331
  });
208
332
  });
209
333
  }
334
+ /**
335
+ * Reconciles all in-memory CoValues with the given peer.
336
+ * Creates a subscription for each CoValue that is not already subscribed to.
337
+ */
210
338
  startPeerReconciliation(peer) {
211
339
  if (isPersistentServerPeer(peer)) {
212
340
  // Resume syncing unsynced CoValues asynchronously
213
341
  this.resumeUnsyncedCoValues().catch((error) => {
214
342
  logger.warn("Failed to resume unsynced CoValues:", error);
215
343
  });
344
+ // Try to run full storage reconciliation for this peer (scheduled per peer, every 30 days)
345
+ this.maybeStartStorageReconciliationForPeer(peer);
216
346
  }
217
347
  const coValuesOrderedByDependency = [];
218
348
  const seen = new Set();
@@ -360,6 +490,7 @@ export class SyncManager {
360
490
  });
361
491
  peerState.addCloseListener(() => {
362
492
  unsubscribeFromKnownStatesUpdates();
493
+ this.ongoingStorageReconciliationTracker.clearPeer(peer.id);
363
494
  this.peersCounter.add(-1, { role: peer.role });
364
495
  if (!peer.persistent && this.peers[peer.id] === peerState) {
365
496
  this.removePeer(peer.id);
@@ -398,7 +529,7 @@ export class SyncManager {
398
529
  const coValue = this.local.getCoValue(msg.id);
399
530
  // Fast path: CoValue is already in memory
400
531
  if (coValue.isAvailable()) {
401
- this.sendNewContent(msg.id, peer);
532
+ this.sendNewContent(msg.id, peer, true);
402
533
  return;
403
534
  }
404
535
  const peerKnownState = peer.getOptimisticKnownState(msg.id);
@@ -411,7 +542,7 @@ export class SyncManager {
411
542
  coValue.getKnownStateFromStorage((storageKnownState) => {
412
543
  // Race condition: CoValue might have been loaded while we were waiting for storage
413
544
  if (coValue.isAvailable()) {
414
- this.sendNewContent(msg.id, peer);
545
+ this.sendNewContent(msg.id, peer, true);
415
546
  return;
416
547
  }
417
548
  if (!storageKnownState) {
@@ -431,7 +562,7 @@ export class SyncManager {
431
562
  // Even though we responded with KNOWN (client has everything), we need
432
563
  // to establish a subscription so that updates from core flow to us.
433
564
  const serverPeers = this.getServerPeers(msg.id, peer.id);
434
- coValue.loadFromPeers(serverPeers);
565
+ coValue.loadFromPeers(serverPeers, "low-priority");
435
566
  return;
436
567
  }
437
568
  // Peer needs content - do full load from storage
@@ -445,7 +576,7 @@ export class SyncManager {
445
576
  loadFromStorageAndRespond(id, peer, coValue) {
446
577
  coValue.loadFromStorage((found) => {
447
578
  if (found && coValue.isAvailable()) {
448
- this.sendNewContent(id, peer);
579
+ this.sendNewContent(id, peer, true);
449
580
  }
450
581
  else {
451
582
  this.loadFromPeersAndRespond(id, peer, coValue);
@@ -457,7 +588,7 @@ export class SyncManager {
457
588
  */
458
589
  loadFromPeersAndRespond(id, peer, coValue) {
459
590
  const peers = this.getServerPeers(id, peer.id);
460
- coValue.loadFromPeers(peers);
591
+ coValue.loadFromPeers(peers, "immediate");
461
592
  const handleLoadResult = () => {
462
593
  if (coValue.isAvailable()) {
463
594
  this.sendNewContent(id, peer);
@@ -517,7 +648,84 @@ export class SyncManager {
517
648
  if (coValue.isAvailable()) {
518
649
  this.sendNewContent(msg.id, peer);
519
650
  }
520
- peer.trackLoadRequestComplete(coValue);
651
+ else if (coValue.isKnownStateAvailable()) {
652
+ // Validate if content is missing before loading it from storage
653
+ if (!this.syncState.isSynced(peer, msg.id)) {
654
+ this.local.loadCoValueCore(msg.id).then(() => {
655
+ this.sendNewContent(msg.id, peer);
656
+ });
657
+ }
658
+ }
659
+ peer.trackLoadRequestComplete(coValue, "known");
660
+ this.maybeMarkCoValueAsReconciled(peer, msg.id);
661
+ }
662
+ handleReconcile(msg, peer) {
663
+ let remaining = msg.values.length;
664
+ if (remaining === 0) {
665
+ this.trySendToPeer(peer, { action: "reconcile-ack", id: msg.id });
666
+ return;
667
+ }
668
+ const pending = new Set();
669
+ const processEntryDone = () => {
670
+ remaining -= 1;
671
+ if (remaining !== 0) {
672
+ return;
673
+ }
674
+ if (pending.size === 0) {
675
+ this.trySendToPeer(peer, { action: "reconcile-ack", id: msg.id });
676
+ return;
677
+ }
678
+ this.ongoingStorageReconciliationTracker.trackBatch(peer.id, msg.id, pending);
679
+ };
680
+ for (const [coValueId, clientSessionsHash] of msg.values) {
681
+ // Avoid creating a new coValue object if it's not already in memory
682
+ const inMemoryCoValue = this.local.isCoValueInMemory(coValueId)
683
+ ? this.local.getCoValue(coValueId)
684
+ : undefined;
685
+ if (inMemoryCoValue?.isErroredInPeer(peer.id)) {
686
+ processEntryDone();
687
+ continue;
688
+ }
689
+ const maybeSendLoadRequest = (knownState) => {
690
+ if (!knownState) {
691
+ pending.add(coValueId);
692
+ peer.trackToldKnownState(coValueId);
693
+ this.trySendToPeer(peer, {
694
+ action: "load",
695
+ id: coValueId,
696
+ header: false,
697
+ sessions: {},
698
+ });
699
+ }
700
+ else {
701
+ const serverSessionsHash = this.hashKnownStateSessions(knownState.sessions);
702
+ if (serverSessionsHash !== clientSessionsHash) {
703
+ pending.add(coValueId);
704
+ peer.trackToldKnownState(coValueId);
705
+ this.trySendToPeer(peer, { action: "load", ...knownState });
706
+ }
707
+ }
708
+ processEntryDone();
709
+ };
710
+ if (inMemoryCoValue?.isAvailable() ||
711
+ inMemoryCoValue?.loadingState === "onlyKnownState") {
712
+ maybeSendLoadRequest(inMemoryCoValue.knownState());
713
+ }
714
+ else {
715
+ this.local.storage
716
+ ? this.local.storage.loadKnownState(coValueId, maybeSendLoadRequest)
717
+ : maybeSendLoadRequest(undefined);
718
+ }
719
+ }
720
+ }
721
+ handleReconcileAck(msg, peer) {
722
+ const nextOffset = this.reconciliationAckTracker.handleAck(msg.id, peer.id);
723
+ if (nextOffset !== undefined) {
724
+ this.local.storage?.renewStorageReconciliationLock(this.local.currentSessionID, peer.id, nextOffset);
725
+ }
726
+ }
727
+ hashKnownStateSessions(sessions) {
728
+ return this.local.crypto.shortHash(sessions);
521
729
  }
522
730
  recordTransactionsSize(newTransactions, source) {
523
731
  for (const tx of newTransactions) {
@@ -759,7 +967,10 @@ export class SyncManager {
759
967
  this.trackSyncState(coValue.id);
760
968
  }
761
969
  }
762
- peer?.trackLoadRequestComplete(coValue);
970
+ peer?.trackLoadRequestComplete(coValue, "content");
971
+ if (peer && !coValue.isStreaming()) {
972
+ this.maybeMarkCoValueAsReconciled(peer, msg.id);
973
+ }
763
974
  for (const peer of this.getPeers(coValue.id)) {
764
975
  /**
765
976
  * We sync the content against the source peer if it is a client or server peers
@@ -774,7 +985,7 @@ export class SyncManager {
774
985
  this.sendNewContent(coValue.id, peer);
775
986
  }
776
987
  else if (peer.role === "server") {
777
- peer.sendLoadRequest(coValue);
988
+ peer.sendLoadRequest(coValue, "low-priority");
778
989
  }
779
990
  }
780
991
  }
@@ -782,6 +993,15 @@ export class SyncManager {
782
993
  peer.setKnownState(msg.id, knownStateFrom(msg));
783
994
  return this.sendNewContent(msg.id, peer);
784
995
  }
996
+ maybeMarkCoValueAsReconciled(peer, coValueId) {
997
+ const completedBatchIds = this.ongoingStorageReconciliationTracker.markItemComplete(peer.id, coValueId);
998
+ for (const batchId of completedBatchIds) {
999
+ this.trySendToPeer(peer, {
1000
+ action: "reconcile-ack",
1001
+ id: batchId,
1002
+ });
1003
+ }
1004
+ }
785
1005
  syncContent(content) {
786
1006
  const coValue = this.local.getCoValue(content.id);
787
1007
  this.storeContent(content);
@@ -949,6 +1169,42 @@ export class SyncManager {
949
1169
  return this.unsyncedTracker.forcePersist();
950
1170
  }
951
1171
  }
1172
+ _SyncManager_instances = new WeakSet(), _SyncManager_sendNewContent = function _SyncManager_sendNewContent(id, peer, seen, forceKnownReplyOnNoDelta) {
1173
+ if (seen.has(id)) {
1174
+ return;
1175
+ }
1176
+ seen.add(id);
1177
+ const coValue = this.local.getCoValue(id);
1178
+ if (!coValue.isAvailable()) {
1179
+ return;
1180
+ }
1181
+ const includeDependencies = peer.role !== "server";
1182
+ if (includeDependencies) {
1183
+ for (const dependency of coValue.getDependedOnCoValues()) {
1184
+ __classPrivateFieldGet(this, _SyncManager_instances, "m", _SyncManager_sendNewContent).call(this, dependency, peer, seen, false);
1185
+ }
1186
+ }
1187
+ const newContentPieces = coValue.newContentSince(peer.getOptimisticKnownState(id));
1188
+ if (newContentPieces) {
1189
+ for (const piece of newContentPieces) {
1190
+ this.trySendToPeer(peer, piece);
1191
+ }
1192
+ peer.combineOptimisticWith(id, coValue.knownState());
1193
+ }
1194
+ else if (forceKnownReplyOnNoDelta || !peer.toldKnownState.has(id)) {
1195
+ if (coValue.isDeleted) {
1196
+ // This way we make the peer believe that we've always ingested all the content they sent, even though we skipped it because the coValue is deleted
1197
+ this.trySendToPeer(peer, coValue.stopSyncingKnownStateMessage(peer.getKnownState(id)));
1198
+ }
1199
+ else {
1200
+ this.trySendToPeer(peer, {
1201
+ action: "known",
1202
+ ...coValue.knownStateWithStreaming(),
1203
+ });
1204
+ }
1205
+ }
1206
+ peer.trackToldKnownState(id);
1207
+ };
952
1208
  /**
953
1209
  * Returns a ServerPeerSelector that implements the Highest Weighted Random (HWR) algorithm.
954
1210
  *