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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/OngoingStorageReconciliationTracker.d.ts +16 -0
- package/dist/OngoingStorageReconciliationTracker.d.ts.map +1 -0
- package/dist/OngoingStorageReconciliationTracker.js +75 -0
- package/dist/OngoingStorageReconciliationTracker.js.map +1 -0
- package/dist/PeerState.d.ts +2 -2
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +3 -3
- package/dist/PeerState.js.map +1 -1
- package/dist/StorageReconciliationAckTracker.d.ts +14 -0
- package/dist/StorageReconciliationAckTracker.d.ts.map +1 -0
- package/dist/StorageReconciliationAckTracker.js +72 -0
- package/dist/StorageReconciliationAckTracker.js.map +1 -0
- package/dist/SyncStateManager.js +2 -2
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +2 -1
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +43 -10
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValues/coList.d.ts +2 -0
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +28 -0
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts +4 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +15 -1
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/exports.d.ts +9 -1
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +5 -1
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +7 -3
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +13 -5
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/queue/LinkedList.d.ts +2 -0
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js +7 -0
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/OutgoingLoadQueue.d.ts +4 -1
- package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -1
- package/dist/queue/OutgoingLoadQueue.js +41 -13
- package/dist/queue/OutgoingLoadQueue.js.map +1 -1
- package/dist/queue/PriorityBasedMessageQueue.d.ts +1 -0
- package/dist/queue/PriorityBasedMessageQueue.d.ts.map +1 -1
- package/dist/queue/PriorityBasedMessageQueue.js +11 -1
- package/dist/queue/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/storage/knownState.d.ts +2 -0
- package/dist/storage/knownState.d.ts.map +1 -1
- package/dist/storage/knownState.js +11 -0
- package/dist/storage/knownState.js.map +1 -1
- package/dist/storage/sqlite/client.d.ts +10 -1
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +84 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +11 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +10 -1
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +86 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +9 -2
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +19 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +9 -2
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +20 -13
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +64 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts +53 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +300 -44
- package/dist/sync.js.map +1 -1
- package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts +2 -0
- package/dist/tests/OngoingStorageReconciliationTracker.test.d.ts.map +1 -0
- package/dist/tests/OngoingStorageReconciliationTracker.test.js +60 -0
- package/dist/tests/OngoingStorageReconciliationTracker.test.js.map +1 -0
- package/dist/tests/OutgoingLoadQueue.test.js +137 -39
- package/dist/tests/OutgoingLoadQueue.test.js.map +1 -1
- package/dist/tests/SQLiteClientAsync.test.js +1 -1
- package/dist/tests/SQLiteClientAsync.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +138 -0
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +154 -0
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/StorageReconciliationAckTracker.test.d.ts +2 -0
- package/dist/tests/StorageReconciliationAckTracker.test.d.ts.map +1 -0
- package/dist/tests/StorageReconciliationAckTracker.test.js +74 -0
- package/dist/tests/StorageReconciliationAckTracker.test.js.map +1 -0
- package/dist/tests/SyncStateManager.test.js +18 -0
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/coList.test.js +112 -1
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +36 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/group.test.js +44 -0
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +6 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +4 -0
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/sync.concurrentLoad.test.js +333 -1
- package/dist/tests/sync.concurrentLoad.test.js.map +1 -1
- package/dist/tests/sync.garbageCollection.test.js +4 -0
- package/dist/tests/sync.garbageCollection.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +19 -0
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +1 -0
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.multipleServers.test.js +41 -3
- package/dist/tests/sync.multipleServers.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +2 -0
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +1 -0
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.storageReconciliation.test.d.ts +2 -0
- package/dist/tests/sync.storageReconciliation.test.d.ts.map +1 -0
- package/dist/tests/sync.storageReconciliation.test.js +502 -0
- package/dist/tests/sync.storageReconciliation.test.js.map +1 -0
- package/dist/tests/testUtils.d.ts +1 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +3 -2
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +4 -4
- package/src/OngoingStorageReconciliationTracker.ts +97 -0
- package/src/PeerState.ts +10 -3
- package/src/StorageReconciliationAckTracker.ts +83 -0
- package/src/SyncStateManager.ts +3 -3
- package/src/coValueCore/coValueCore.ts +47 -16
- package/src/coValues/coList.ts +23 -0
- package/src/coValues/group.ts +18 -0
- package/src/config.ts +18 -0
- package/src/exports.ts +8 -0
- package/src/localNode.ts +18 -0
- package/src/permissions.ts +1 -1
- package/src/queue/LinkedList.ts +10 -0
- package/src/queue/OutgoingLoadQueue.ts +57 -15
- package/src/queue/PriorityBasedMessageQueue.ts +15 -1
- package/src/storage/knownState.ts +14 -0
- package/src/storage/sqlite/client.ts +128 -0
- package/src/storage/sqlite/sqliteMigrations.ts +11 -0
- package/src/storage/sqliteAsync/client.ts +139 -0
- package/src/storage/storageAsync.ts +37 -0
- package/src/storage/storageSync.ts +41 -16
- package/src/storage/types.ts +110 -0
- package/src/sync.ts +359 -14
- package/src/tests/OngoingStorageReconciliationTracker.test.ts +85 -0
- package/src/tests/OutgoingLoadQueue.test.ts +226 -59
- package/src/tests/SQLiteClientAsync.test.ts +1 -1
- package/src/tests/StorageApiAsync.test.ts +161 -1
- package/src/tests/StorageApiSync.test.ts +176 -0
- package/src/tests/StorageReconciliationAckTracker.test.ts +99 -0
- package/src/tests/SyncStateManager.test.ts +25 -0
- package/src/tests/coList.test.ts +138 -0
- package/src/tests/coValueCore.loadFromStorage.test.ts +72 -1
- package/src/tests/group.test.ts +87 -0
- package/src/tests/knownState.lazyLoading.test.ts +36 -1
- package/src/tests/messagesTestUtils.ts +4 -0
- package/src/tests/sync.concurrentLoad.test.ts +491 -0
- package/src/tests/sync.garbageCollection.test.ts +4 -0
- package/src/tests/sync.load.test.ts +26 -0
- package/src/tests/sync.mesh.test.ts +1 -0
- package/src/tests/sync.multipleServers.test.ts +60 -2
- package/src/tests/sync.storage.test.ts +2 -0
- package/src/tests/sync.storageAsync.test.ts +1 -0
- package/src/tests/sync.storageReconciliation.test.ts +696 -0
- 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,
|
|
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: () => {
|
package/dist/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"
|
|
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,
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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: "
|
|
142
|
-
|
|
184
|
+
action: "reconcile",
|
|
185
|
+
id: batchId,
|
|
186
|
+
values: entries,
|
|
143
187
|
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
*
|