cojson 0.16.3 → 0.16.5
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 +20 -0
- package/dist/coValue.d.ts +1 -1
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +10 -0
- package/dist/coValueContentMessage.d.ts.map +1 -0
- package/dist/coValueContentMessage.js +46 -0
- package/dist/coValueContentMessage.js.map +1 -0
- package/dist/coValueCore/coValueCore.d.ts +6 -10
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +20 -125
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +1 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +14 -27
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/group.d.ts +18 -10
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +237 -67
- package/dist/coValues/group.js.map +1 -1
- package/dist/ids.d.ts +3 -3
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/localNode.d.ts +11 -6
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +7 -2
- package/dist/localNode.js.map +1 -1
- package/dist/queue/LocalTransactionsSyncQueue.d.ts +24 -0
- package/dist/queue/LocalTransactionsSyncQueue.d.ts.map +1 -0
- package/dist/queue/LocalTransactionsSyncQueue.js +55 -0
- package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -0
- package/dist/queue/StoreQueue.d.ts +9 -6
- package/dist/queue/StoreQueue.d.ts.map +1 -1
- package/dist/queue/StoreQueue.js +10 -2
- package/dist/queue/StoreQueue.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +11 -3
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +59 -46
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +9 -3
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +48 -35
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/syncUtils.d.ts +2 -1
- package/dist/storage/syncUtils.d.ts.map +1 -1
- package/dist/storage/syncUtils.js +4 -0
- package/dist/storage/syncUtils.js.map +1 -1
- package/dist/storage/types.d.ts +3 -2
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/sync.d.ts +6 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +33 -56
- package/dist/sync.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.d.ts +2 -0
- package/dist/tests/StorageApiAsync.test.d.ts.map +1 -0
- package/dist/tests/StorageApiAsync.test.js +574 -0
- package/dist/tests/StorageApiAsync.test.js.map +1 -0
- package/dist/tests/StorageApiSync.test.d.ts +2 -0
- package/dist/tests/StorageApiSync.test.d.ts.map +1 -0
- package/dist/tests/StorageApiSync.test.js +426 -0
- package/dist/tests/StorageApiSync.test.js.map +1 -0
- package/dist/tests/StoreQueue.test.js +9 -21
- package/dist/tests/StoreQueue.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +18 -8
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/group.inheritance.test.js +274 -2
- package/dist/tests/group.inheritance.test.js.map +1 -1
- package/dist/tests/group.removeMember.test.js +152 -1
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/group.roleOf.test.js +2 -2
- package/dist/tests/group.roleOf.test.js.map +1 -1
- package/dist/tests/group.test.js +81 -3
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/sync.auth.test.js +22 -10
- package/dist/tests/sync.auth.test.js.map +1 -1
- package/dist/tests/sync.load.test.js +30 -25
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +12 -6
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +6 -4
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +8 -14
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.storageAsync.test.js +31 -14
- package/dist/tests/sync.storageAsync.test.js.map +1 -1
- package/dist/tests/sync.test.js +5 -9
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +31 -1
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +2 -3
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +16 -8
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +4 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +22 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts +2 -2
- package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts.map +1 -1
- package/dist/typeUtils/expectGroup.d.ts.map +1 -1
- package/dist/typeUtils/expectGroup.js +6 -5
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/coValue.ts +1 -4
- package/src/coValueContentMessage.ts +73 -0
- package/src/coValueCore/coValueCore.ts +36 -192
- package/src/coValueCore/verifiedState.ts +28 -35
- package/src/coValues/group.ts +329 -99
- package/src/ids.ts +3 -3
- package/src/localNode.ts +15 -10
- package/src/queue/LocalTransactionsSyncQueue.ts +96 -0
- package/src/queue/StoreQueue.ts +22 -12
- package/src/storage/storageAsync.ts +78 -56
- package/src/storage/storageSync.ts +66 -45
- package/src/storage/syncUtils.ts +9 -1
- package/src/storage/types.ts +6 -5
- package/src/sync.ts +47 -67
- package/src/tests/StorageApiAsync.test.ts +829 -0
- package/src/tests/StorageApiSync.test.ts +628 -0
- package/src/tests/StoreQueue.test.ts +10 -24
- package/src/tests/SyncStateManager.test.ts +22 -21
- package/src/tests/group.inheritance.test.ts +415 -1
- package/src/tests/group.removeMember.test.ts +244 -1
- package/src/tests/group.roleOf.test.ts +2 -2
- package/src/tests/group.test.ts +105 -5
- package/src/tests/sync.auth.test.ts +22 -10
- package/src/tests/sync.load.test.ts +32 -26
- package/src/tests/sync.mesh.test.ts +12 -6
- package/src/tests/sync.peerReconciliation.test.ts +6 -4
- package/src/tests/sync.storage.test.ts +8 -14
- package/src/tests/sync.storageAsync.test.ts +39 -14
- package/src/tests/sync.test.ts +6 -14
- package/src/tests/sync.upload.test.ts +38 -1
- package/src/tests/testStorage.ts +19 -13
- package/src/tests/testUtils.ts +29 -5
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -2
- package/src/typeUtils/expectGroup.ts +8 -5
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addTransactionToContentMessage,
|
|
3
|
+
createContentMessage,
|
|
4
|
+
} from "../coValueContentMessage.js";
|
|
5
|
+
import { Transaction, VerifiedState } from "../coValueCore/verifiedState.js";
|
|
6
|
+
import { Signature } from "../crypto/crypto.js";
|
|
7
|
+
import { SessionID } from "../ids.js";
|
|
8
|
+
import { NewContentMessage } from "../sync.js";
|
|
9
|
+
import { LinkedList } from "./LinkedList.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This queue is used to batch the sync of local transactions while preserving the order of updates between CoValues.
|
|
13
|
+
*
|
|
14
|
+
* We need to preserve the order of updates between CoValues to keep the state always consistent in case of shutdown in the middle of a sync.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* 1. When we extend a Group we need to always ensure that the parent group is persisted before persisting the extension transaction.
|
|
18
|
+
* 2. If we do multiple updates on the same CoMap, the updates will be batched because it's safe to do so.
|
|
19
|
+
*/
|
|
20
|
+
export class LocalTransactionsSyncQueue {
|
|
21
|
+
private readonly queue = new LinkedList<NewContentMessage>();
|
|
22
|
+
|
|
23
|
+
constructor(private readonly sync: (content: NewContentMessage) => void) {}
|
|
24
|
+
|
|
25
|
+
syncHeader = (coValue: VerifiedState) => {
|
|
26
|
+
const lastPendingSync = this.queue.tail?.value;
|
|
27
|
+
|
|
28
|
+
if (lastPendingSync?.id === coValue.id) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.enqueue(createContentMessage(coValue.id, coValue.header));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
syncTransaction = (
|
|
36
|
+
coValue: VerifiedState,
|
|
37
|
+
transaction: Transaction,
|
|
38
|
+
sessionID: SessionID,
|
|
39
|
+
signature: Signature,
|
|
40
|
+
txIdx: number,
|
|
41
|
+
) => {
|
|
42
|
+
const lastPendingSync = this.queue.tail?.value;
|
|
43
|
+
const lastSignatureIdx = coValue.getLastSignatureCheckpoint(sessionID);
|
|
44
|
+
const isSignatureCheckpoint =
|
|
45
|
+
lastSignatureIdx > -1 && lastSignatureIdx === txIdx - 1;
|
|
46
|
+
|
|
47
|
+
if (lastPendingSync?.id === coValue.id && !isSignatureCheckpoint) {
|
|
48
|
+
addTransactionToContentMessage(
|
|
49
|
+
lastPendingSync,
|
|
50
|
+
transaction,
|
|
51
|
+
sessionID,
|
|
52
|
+
signature,
|
|
53
|
+
txIdx,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = createContentMessage(coValue.id, coValue.header, false);
|
|
60
|
+
|
|
61
|
+
addTransactionToContentMessage(
|
|
62
|
+
content,
|
|
63
|
+
transaction,
|
|
64
|
+
sessionID,
|
|
65
|
+
signature,
|
|
66
|
+
txIdx,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
this.enqueue(content);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
enqueue(content: NewContentMessage) {
|
|
73
|
+
this.queue.push(content);
|
|
74
|
+
|
|
75
|
+
this.processPendingSyncs();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private processingSyncs = false;
|
|
79
|
+
processPendingSyncs() {
|
|
80
|
+
if (this.processingSyncs) return;
|
|
81
|
+
|
|
82
|
+
this.processingSyncs = true;
|
|
83
|
+
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
while (this.queue.head) {
|
|
86
|
+
const content = this.queue.head.value;
|
|
87
|
+
|
|
88
|
+
this.sync(content);
|
|
89
|
+
|
|
90
|
+
this.queue.shift();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.processingSyncs = false;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/queue/StoreQueue.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import { CorrectionCallback } from "../exports.js";
|
|
1
2
|
import { logger } from "../logger.js";
|
|
2
|
-
import {
|
|
3
|
+
import { NewContentMessage } from "../sync.js";
|
|
3
4
|
import { LinkedList } from "./LinkedList.js";
|
|
4
5
|
|
|
5
6
|
type StoreQueueEntry = {
|
|
6
|
-
data: NewContentMessage
|
|
7
|
-
correctionCallback:
|
|
7
|
+
data: NewContentMessage;
|
|
8
|
+
correctionCallback: CorrectionCallback;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
export class StoreQueue {
|
|
11
12
|
private queue = new LinkedList<StoreQueueEntry>();
|
|
13
|
+
closed = false;
|
|
14
|
+
|
|
15
|
+
public push(data: NewContentMessage, correctionCallback: CorrectionCallback) {
|
|
16
|
+
if (this.closed) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
12
19
|
|
|
13
|
-
public push(
|
|
14
|
-
data: NewContentMessage[],
|
|
15
|
-
correctionCallback: (data: CoValueKnownState) => void,
|
|
16
|
-
) {
|
|
17
20
|
this.queue.push({ data, correctionCallback });
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -22,12 +25,13 @@ export class StoreQueue {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
processing = false;
|
|
28
|
+
lastCallback: Promise<unknown> | undefined;
|
|
25
29
|
|
|
26
30
|
async processQueue(
|
|
27
31
|
callback: (
|
|
28
|
-
data: NewContentMessage
|
|
29
|
-
correctionCallback:
|
|
30
|
-
) => Promise<
|
|
32
|
+
data: NewContentMessage,
|
|
33
|
+
correctionCallback: CorrectionCallback,
|
|
34
|
+
) => Promise<unknown>,
|
|
31
35
|
) {
|
|
32
36
|
if (this.processing) {
|
|
33
37
|
return;
|
|
@@ -41,16 +45,22 @@ export class StoreQueue {
|
|
|
41
45
|
const { data, correctionCallback } = entry;
|
|
42
46
|
|
|
43
47
|
try {
|
|
44
|
-
|
|
48
|
+
this.lastCallback = callback(data, correctionCallback);
|
|
49
|
+
await this.lastCallback;
|
|
45
50
|
} catch (err) {
|
|
46
51
|
logger.error("Error processing message in store queue", { err });
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
this.lastCallback = undefined;
|
|
50
56
|
this.processing = false;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
close() {
|
|
60
|
+
this.closed = true;
|
|
61
|
+
|
|
54
62
|
while (this.pull()) {}
|
|
63
|
+
|
|
64
|
+
return this.lastCallback;
|
|
55
65
|
}
|
|
56
66
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContentMessage,
|
|
3
|
+
exceedsRecommendedSize,
|
|
4
|
+
getTransactionSize,
|
|
5
|
+
} from "../coValueContentMessage.js";
|
|
1
6
|
import {
|
|
2
7
|
type CoValueCore,
|
|
3
|
-
MAX_RECOMMENDED_TX_SIZE,
|
|
4
8
|
type RawCoID,
|
|
5
9
|
type SessionID,
|
|
6
10
|
type StorageAPI,
|
|
11
|
+
logger,
|
|
7
12
|
} from "../exports.js";
|
|
8
|
-
import { getPriorityFromHeader } from "../priority.js";
|
|
9
13
|
import { StoreQueue } from "../queue/StoreQueue.js";
|
|
10
14
|
import {
|
|
11
15
|
CoValueKnownState,
|
|
@@ -13,8 +17,13 @@ import {
|
|
|
13
17
|
emptyKnownState,
|
|
14
18
|
} from "../sync.js";
|
|
15
19
|
import { StorageKnownState } from "./knownState.js";
|
|
16
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
collectNewTxs,
|
|
22
|
+
getDependedOnCoValues,
|
|
23
|
+
getNewTransactionsSize,
|
|
24
|
+
} from "./syncUtils.js";
|
|
17
25
|
import type {
|
|
26
|
+
CorrectionCallback,
|
|
18
27
|
DBClientInterfaceAsync,
|
|
19
28
|
SignatureAfterRow,
|
|
20
29
|
StoredCoValueRow,
|
|
@@ -82,6 +91,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
82
91
|
);
|
|
83
92
|
|
|
84
93
|
const knownState = this.knwonStates.getKnownState(coValueRow.id);
|
|
94
|
+
knownState.header = true;
|
|
85
95
|
|
|
86
96
|
for (const sessionRow of allCoValueSessions) {
|
|
87
97
|
knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
@@ -89,13 +99,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
89
99
|
|
|
90
100
|
this.loadedCoValues.add(coValueRow.id);
|
|
91
101
|
|
|
92
|
-
let contentMessage =
|
|
93
|
-
action: "content",
|
|
94
|
-
id: coValueRow.id,
|
|
95
|
-
header: coValueRow.header,
|
|
96
|
-
new: {},
|
|
97
|
-
priority: getPriorityFromHeader(coValueRow.header),
|
|
98
|
-
} as NewContentMessage;
|
|
102
|
+
let contentMessage = createContentMessage(coValueRow.id, coValueRow.header);
|
|
99
103
|
|
|
100
104
|
if (contentStreaming) {
|
|
101
105
|
contentMessage.expectContentUntil = knownState["sessions"];
|
|
@@ -136,13 +140,10 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
136
140
|
contentMessage,
|
|
137
141
|
callback,
|
|
138
142
|
);
|
|
139
|
-
contentMessage =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
new: {},
|
|
144
|
-
priority: getPriorityFromHeader(coValueRow.header),
|
|
145
|
-
} satisfies NewContentMessage;
|
|
143
|
+
contentMessage = createContentMessage(
|
|
144
|
+
coValueRow.id,
|
|
145
|
+
coValueRow.header,
|
|
146
|
+
);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
}
|
|
@@ -194,33 +195,64 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
194
195
|
|
|
195
196
|
storeQueue = new StoreQueue();
|
|
196
197
|
|
|
197
|
-
async store(
|
|
198
|
-
msgs: NewContentMessage[],
|
|
199
|
-
correctionCallback: (data: CoValueKnownState) => void,
|
|
200
|
-
) {
|
|
198
|
+
async store(msg: NewContentMessage, correctionCallback: CorrectionCallback) {
|
|
201
199
|
/**
|
|
202
200
|
* The store operations must be done one by one, because we can't start a new transaction when there
|
|
203
201
|
* is already a transaction open.
|
|
204
202
|
*/
|
|
205
|
-
this.storeQueue.push(
|
|
203
|
+
this.storeQueue.push(msg, correctionCallback);
|
|
206
204
|
|
|
207
205
|
this.storeQueue.processQueue(async (data, correctionCallback) => {
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
return this.storeSingle(data, correctionCallback);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
210
209
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
/**
|
|
211
|
+
* This function is called when the storage lacks the information required to store the incoming content.
|
|
212
|
+
*
|
|
213
|
+
* It triggers a `correctionCallback` to ask the syncManager to provide the missing information.
|
|
214
|
+
*
|
|
215
|
+
* The correction is applied immediately, to ensure that, when applicable, the dependent content in the queue won't require additional corrections.
|
|
216
|
+
*/
|
|
217
|
+
private async handleCorrection(
|
|
218
|
+
knownState: CoValueKnownState,
|
|
219
|
+
correctionCallback: CorrectionCallback,
|
|
220
|
+
) {
|
|
221
|
+
const correction = correctionCallback(knownState);
|
|
222
|
+
|
|
223
|
+
if (!correction) {
|
|
224
|
+
logger.error("Correction callback returned undefined", {
|
|
225
|
+
knownState,
|
|
226
|
+
correction: correction ?? null,
|
|
227
|
+
});
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const msg of correction) {
|
|
232
|
+
const success = await this.storeSingle(msg, (knownState) => {
|
|
233
|
+
logger.error("Double correction requested", {
|
|
234
|
+
msg,
|
|
235
|
+
knownState,
|
|
236
|
+
});
|
|
237
|
+
return undefined;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!success) {
|
|
241
|
+
return false;
|
|
216
242
|
}
|
|
217
|
-
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return true;
|
|
218
246
|
}
|
|
219
247
|
|
|
220
248
|
private async storeSingle(
|
|
221
249
|
msg: NewContentMessage,
|
|
222
|
-
correctionCallback:
|
|
250
|
+
correctionCallback: CorrectionCallback,
|
|
223
251
|
): Promise<boolean> {
|
|
252
|
+
if (this.storeQueue.closed) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
224
256
|
const id = msg.id;
|
|
225
257
|
const coValueRow = await this.dbClient.getCoValue(id);
|
|
226
258
|
|
|
@@ -231,8 +263,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
231
263
|
const knownState = emptyKnownState(id as RawCoID);
|
|
232
264
|
this.knwonStates.setKnownState(id, knownState);
|
|
233
265
|
|
|
234
|
-
|
|
235
|
-
return false;
|
|
266
|
+
return this.handleCorrection(knownState, correctionCallback);
|
|
236
267
|
}
|
|
237
268
|
|
|
238
269
|
const storedCoValueRowID: number = coValueRow
|
|
@@ -276,8 +307,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
276
307
|
this.knwonStates.handleUpdate(id, knownState);
|
|
277
308
|
|
|
278
309
|
if (invalidAssumptions) {
|
|
279
|
-
|
|
280
|
-
return false;
|
|
310
|
+
return this.handleCorrection(knownState, correctionCallback);
|
|
281
311
|
}
|
|
282
312
|
|
|
283
313
|
return true;
|
|
@@ -290,38 +320,31 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
290
320
|
storedCoValueRowID: number,
|
|
291
321
|
) {
|
|
292
322
|
const newTransactions = msg.new[sessionID]?.newTransactions || [];
|
|
323
|
+
const lastIdx = sessionRow?.lastIdx || 0;
|
|
293
324
|
|
|
294
|
-
const actuallyNewOffset =
|
|
295
|
-
(sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
|
|
325
|
+
const actuallyNewOffset = lastIdx - (msg.new[sessionID]?.after || 0);
|
|
296
326
|
|
|
297
327
|
const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
|
|
298
328
|
|
|
299
329
|
if (actuallyNewTransactions.length === 0) {
|
|
300
|
-
return
|
|
330
|
+
return lastIdx;
|
|
301
331
|
}
|
|
302
332
|
|
|
303
|
-
let
|
|
304
|
-
|
|
305
|
-
actuallyNewTransactions.reduce(
|
|
306
|
-
(sum, tx) =>
|
|
307
|
-
sum +
|
|
308
|
-
(tx.privacy === "private"
|
|
309
|
-
? tx.encryptedChanges.length
|
|
310
|
-
: tx.changes.length),
|
|
311
|
-
0,
|
|
312
|
-
);
|
|
333
|
+
let bytesSinceLastSignature = sessionRow?.bytesSinceLastSignature || 0;
|
|
334
|
+
const newTransactionsSize = getNewTransactionsSize(actuallyNewTransactions);
|
|
313
335
|
|
|
314
|
-
const newLastIdx =
|
|
315
|
-
(sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
|
|
336
|
+
const newLastIdx = lastIdx + actuallyNewTransactions.length;
|
|
316
337
|
|
|
317
338
|
let shouldWriteSignature = false;
|
|
318
339
|
|
|
319
|
-
if (
|
|
340
|
+
if (exceedsRecommendedSize(bytesSinceLastSignature, newTransactionsSize)) {
|
|
320
341
|
shouldWriteSignature = true;
|
|
321
|
-
|
|
342
|
+
bytesSinceLastSignature = 0;
|
|
343
|
+
} else {
|
|
344
|
+
bytesSinceLastSignature += newTransactionsSize;
|
|
322
345
|
}
|
|
323
346
|
|
|
324
|
-
const nextIdx =
|
|
347
|
+
const nextIdx = lastIdx;
|
|
325
348
|
|
|
326
349
|
if (!msg.new[sessionID]) throw new Error("Session ID not found");
|
|
327
350
|
|
|
@@ -330,7 +353,7 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
330
353
|
sessionID,
|
|
331
354
|
lastIdx: newLastIdx,
|
|
332
355
|
lastSignature: msg.new[sessionID].lastSignature,
|
|
333
|
-
bytesSinceLastSignature
|
|
356
|
+
bytesSinceLastSignature,
|
|
334
357
|
};
|
|
335
358
|
|
|
336
359
|
const sessionRowID: number = await this.dbClient.addSessionUpdate({
|
|
@@ -360,7 +383,6 @@ export class StorageApiAsync implements StorageAPI {
|
|
|
360
383
|
}
|
|
361
384
|
|
|
362
385
|
close() {
|
|
363
|
-
|
|
364
|
-
this.storeQueue.drain();
|
|
386
|
+
return this.storeQueue.close();
|
|
365
387
|
}
|
|
366
388
|
}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { UpDownCounter, metrics } from "@opentelemetry/api";
|
|
2
|
+
import {
|
|
3
|
+
createContentMessage,
|
|
4
|
+
exceedsRecommendedSize,
|
|
5
|
+
getTransactionSize,
|
|
6
|
+
} from "../coValueContentMessage.js";
|
|
2
7
|
import {
|
|
3
8
|
CoValueCore,
|
|
4
|
-
MAX_RECOMMENDED_TX_SIZE,
|
|
5
9
|
RawCoID,
|
|
6
10
|
type SessionID,
|
|
7
11
|
type StorageAPI,
|
|
12
|
+
logger,
|
|
8
13
|
} from "../exports.js";
|
|
9
|
-
import { getPriorityFromHeader } from "../priority.js";
|
|
10
14
|
import {
|
|
11
15
|
CoValueKnownState,
|
|
12
16
|
NewContentMessage,
|
|
13
17
|
emptyKnownState,
|
|
14
18
|
} from "../sync.js";
|
|
15
19
|
import { StorageKnownState } from "./knownState.js";
|
|
16
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
collectNewTxs,
|
|
22
|
+
getDependedOnCoValues,
|
|
23
|
+
getNewTransactionsSize,
|
|
24
|
+
} from "./syncUtils.js";
|
|
17
25
|
import type {
|
|
26
|
+
CorrectionCallback,
|
|
18
27
|
DBClientInterfaceSync,
|
|
19
28
|
SignatureAfterRow,
|
|
20
29
|
StoredCoValueRow,
|
|
@@ -84,6 +93,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
const knownState = this.knwonStates.getKnownState(coValueRow.id);
|
|
96
|
+
knownState.header = true;
|
|
87
97
|
|
|
88
98
|
for (const sessionRow of allCoValueSessions) {
|
|
89
99
|
knownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
|
@@ -91,13 +101,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
91
101
|
|
|
92
102
|
this.loadedCoValues.add(coValueRow.id);
|
|
93
103
|
|
|
94
|
-
let contentMessage =
|
|
95
|
-
action: "content",
|
|
96
|
-
id: coValueRow.id,
|
|
97
|
-
header: coValueRow.header,
|
|
98
|
-
new: {},
|
|
99
|
-
priority: getPriorityFromHeader(coValueRow.header),
|
|
100
|
-
} as NewContentMessage;
|
|
104
|
+
let contentMessage = createContentMessage(coValueRow.id, coValueRow.header);
|
|
101
105
|
|
|
102
106
|
if (contentStreaming) {
|
|
103
107
|
this.streamingCounter.add(1);
|
|
@@ -137,13 +141,10 @@ export class StorageApiSync implements StorageAPI {
|
|
|
137
141
|
contentMessage,
|
|
138
142
|
callback,
|
|
139
143
|
);
|
|
140
|
-
contentMessage =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
new: {},
|
|
145
|
-
priority: getPriorityFromHeader(coValueRow.header),
|
|
146
|
-
} satisfies NewContentMessage;
|
|
144
|
+
contentMessage = createContentMessage(
|
|
145
|
+
coValueRow.id,
|
|
146
|
+
coValueRow.header,
|
|
147
|
+
);
|
|
147
148
|
|
|
148
149
|
// Introduce a delay to not block the main thread
|
|
149
150
|
// for the entire content processing
|
|
@@ -189,22 +190,49 @@ export class StorageApiSync implements StorageAPI {
|
|
|
189
190
|
pushCallback(contentMessage);
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
store(
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
store(msg: NewContentMessage, correctionCallback: CorrectionCallback) {
|
|
194
|
+
return this.storeSingle(msg, correctionCallback);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* This function is called when the storage lacks the information required to store the incoming content.
|
|
199
|
+
*
|
|
200
|
+
* It triggers a `correctionCallback` to ask the syncManager to provide the missing information.
|
|
201
|
+
*/
|
|
202
|
+
private handleCorrection(
|
|
203
|
+
knownState: CoValueKnownState,
|
|
204
|
+
correctionCallback: CorrectionCallback,
|
|
195
205
|
) {
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
const correction = correctionCallback(knownState);
|
|
207
|
+
|
|
208
|
+
if (!correction) {
|
|
209
|
+
logger.error("Correction callback returned undefined", {
|
|
210
|
+
knownState,
|
|
211
|
+
correction: correction ?? null,
|
|
212
|
+
});
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const msg of correction) {
|
|
217
|
+
const success = this.storeSingle(msg, (knownState) => {
|
|
218
|
+
logger.error("Double correction requested", {
|
|
219
|
+
msg,
|
|
220
|
+
knownState,
|
|
221
|
+
});
|
|
222
|
+
return undefined;
|
|
223
|
+
});
|
|
198
224
|
|
|
199
225
|
if (!success) {
|
|
200
226
|
return false;
|
|
201
227
|
}
|
|
202
228
|
}
|
|
229
|
+
|
|
230
|
+
return true;
|
|
203
231
|
}
|
|
204
232
|
|
|
205
233
|
private storeSingle(
|
|
206
234
|
msg: NewContentMessage,
|
|
207
|
-
correctionCallback:
|
|
235
|
+
correctionCallback: CorrectionCallback,
|
|
208
236
|
): boolean {
|
|
209
237
|
const id = msg.id;
|
|
210
238
|
const coValueRow = this.dbClient.getCoValue(id);
|
|
@@ -214,11 +242,9 @@ export class StorageApiSync implements StorageAPI {
|
|
|
214
242
|
|
|
215
243
|
if (invalidAssumptionOnHeaderPresence) {
|
|
216
244
|
const knownState = emptyKnownState(id as RawCoID);
|
|
217
|
-
correctionCallback(knownState);
|
|
218
|
-
|
|
219
245
|
this.knwonStates.setKnownState(id, knownState);
|
|
220
246
|
|
|
221
|
-
return
|
|
247
|
+
return this.handleCorrection(knownState, correctionCallback);
|
|
222
248
|
}
|
|
223
249
|
|
|
224
250
|
const storedCoValueRowID: number = coValueRow
|
|
@@ -258,8 +284,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
258
284
|
this.knwonStates.handleUpdate(id, knownState);
|
|
259
285
|
|
|
260
286
|
if (invalidAssumptions) {
|
|
261
|
-
|
|
262
|
-
return false;
|
|
287
|
+
return this.handleCorrection(knownState, correctionCallback);
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
return true;
|
|
@@ -272,35 +297,29 @@ export class StorageApiSync implements StorageAPI {
|
|
|
272
297
|
storedCoValueRowID: number,
|
|
273
298
|
) {
|
|
274
299
|
const newTransactions = msg.new[sessionID]?.newTransactions || [];
|
|
300
|
+
const lastIdx = sessionRow?.lastIdx || 0;
|
|
275
301
|
|
|
276
|
-
const actuallyNewOffset =
|
|
277
|
-
(sessionRow?.lastIdx || 0) - (msg.new[sessionID]?.after || 0);
|
|
302
|
+
const actuallyNewOffset = lastIdx - (msg.new[sessionID]?.after || 0);
|
|
278
303
|
|
|
279
304
|
const actuallyNewTransactions = newTransactions.slice(actuallyNewOffset);
|
|
280
305
|
|
|
281
306
|
if (actuallyNewTransactions.length === 0) {
|
|
282
|
-
return
|
|
307
|
+
return lastIdx;
|
|
283
308
|
}
|
|
284
309
|
|
|
285
|
-
let
|
|
286
|
-
|
|
287
|
-
actuallyNewTransactions.reduce(
|
|
288
|
-
(sum, tx) =>
|
|
289
|
-
sum +
|
|
290
|
-
(tx.privacy === "private"
|
|
291
|
-
? tx.encryptedChanges.length
|
|
292
|
-
: tx.changes.length),
|
|
293
|
-
0,
|
|
294
|
-
);
|
|
310
|
+
let bytesSinceLastSignature = sessionRow?.bytesSinceLastSignature || 0;
|
|
311
|
+
const newTransactionsSize = getNewTransactionsSize(actuallyNewTransactions);
|
|
295
312
|
|
|
296
313
|
const newLastIdx =
|
|
297
314
|
(sessionRow?.lastIdx || 0) + actuallyNewTransactions.length;
|
|
298
315
|
|
|
299
316
|
let shouldWriteSignature = false;
|
|
300
317
|
|
|
301
|
-
if (
|
|
318
|
+
if (exceedsRecommendedSize(bytesSinceLastSignature, newTransactionsSize)) {
|
|
302
319
|
shouldWriteSignature = true;
|
|
303
|
-
|
|
320
|
+
bytesSinceLastSignature = 0;
|
|
321
|
+
} else {
|
|
322
|
+
bytesSinceLastSignature += newTransactionsSize;
|
|
304
323
|
}
|
|
305
324
|
|
|
306
325
|
const nextIdx = sessionRow?.lastIdx || 0;
|
|
@@ -312,7 +331,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
312
331
|
sessionID,
|
|
313
332
|
lastIdx: newLastIdx,
|
|
314
333
|
lastSignature: msg.new[sessionID].lastSignature,
|
|
315
|
-
bytesSinceLastSignature
|
|
334
|
+
bytesSinceLastSignature,
|
|
316
335
|
};
|
|
317
336
|
|
|
318
337
|
const sessionRowID: number = this.dbClient.addSessionUpdate({
|
|
@@ -339,5 +358,7 @@ export class StorageApiSync implements StorageAPI {
|
|
|
339
358
|
return this.knwonStates.waitForSync(id, coValue);
|
|
340
359
|
}
|
|
341
360
|
|
|
342
|
-
close() {
|
|
361
|
+
close() {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
343
364
|
}
|
package/src/storage/syncUtils.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { getTransactionSize } from "../coValueContentMessage.js";
|
|
1
2
|
import { getDependedOnCoValuesFromRawData } from "../coValueCore/utils.js";
|
|
2
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CoValueHeader,
|
|
5
|
+
Transaction,
|
|
6
|
+
} from "../coValueCore/verifiedState.js";
|
|
3
7
|
import type { Signature } from "../crypto/crypto.js";
|
|
4
8
|
import type { SessionID } from "../exports.js";
|
|
5
9
|
import type { NewContentMessage } from "../sync.js";
|
|
@@ -48,3 +52,7 @@ export function getDependedOnCoValues(
|
|
|
48
52
|
|
|
49
53
|
return getDependedOnCoValuesFromRawData(id, header, sessionIDs, transactions);
|
|
50
54
|
}
|
|
55
|
+
|
|
56
|
+
export function getNewTransactionsSize(newTxs: Transaction[]) {
|
|
57
|
+
return newTxs.reduce((sum, tx) => sum + getTransactionSize(tx), 0);
|
|
58
|
+
}
|
package/src/storage/types.ts
CHANGED
|
@@ -6,6 +6,10 @@ import { Signature } from "../crypto/crypto.js";
|
|
|
6
6
|
import type { CoValueCore, RawCoID, SessionID } from "../exports.js";
|
|
7
7
|
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
8
8
|
|
|
9
|
+
export type CorrectionCallback = (
|
|
10
|
+
correction: CoValueKnownState,
|
|
11
|
+
) => NewContentMessage[] | undefined;
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* The StorageAPI is the interface that the StorageSync and StorageAsync classes implement.
|
|
11
15
|
*
|
|
@@ -18,16 +22,13 @@ export interface StorageAPI {
|
|
|
18
22
|
callback: (data: NewContentMessage) => void,
|
|
19
23
|
done?: (found: boolean) => void,
|
|
20
24
|
): void;
|
|
21
|
-
store(
|
|
22
|
-
data: NewContentMessage[] | undefined,
|
|
23
|
-
handleCorrection: (correction: CoValueKnownState) => void,
|
|
24
|
-
): void;
|
|
25
|
+
store(data: NewContentMessage, handleCorrection: CorrectionCallback): void;
|
|
25
26
|
|
|
26
27
|
getKnownState(id: string): CoValueKnownState;
|
|
27
28
|
|
|
28
29
|
waitForSync(id: string, coValue: CoValueCore): Promise<void>;
|
|
29
30
|
|
|
30
|
-
close():
|
|
31
|
+
close(): Promise<unknown> | undefined;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export type CoValueRow = {
|