cojson 0.16.3 → 0.16.4
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 +7 -0
- package/dist/coValue.d.ts +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.map +1 -1
- package/dist/coValueCore/coValueCore.js +5 -3
- 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.map +1 -1
- package/dist/coValues/group.js +16 -8
- package/dist/coValues/group.js.map +1 -1
- package/dist/localNode.d.ts +6 -1
- 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 +79 -2
- package/dist/tests/group.inheritance.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 +25 -23
- 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 +3 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +17 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/coValueContentMessage.ts +73 -0
- package/src/coValueCore/coValueCore.ts +14 -5
- package/src/coValueCore/verifiedState.ts +28 -35
- package/src/coValues/group.ts +20 -9
- package/src/localNode.ts +8 -3
- 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 +136 -1
- package/src/tests/sync.auth.test.ts +22 -10
- package/src/tests/sync.load.test.ts +27 -24
- 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 +24 -5
|
@@ -2,9 +2,9 @@ import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
|
|
|
2
2
|
import { Result, err } from "neverthrow";
|
|
3
3
|
import { PeerState } from "../PeerState.js";
|
|
4
4
|
import { RawCoValue } from "../coValue.js";
|
|
5
|
-
import { ControlledAccountOrAgent
|
|
5
|
+
import { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
6
6
|
import { RawGroup } from "../coValues/group.js";
|
|
7
|
-
import { CO_VALUE_LOADING_CONFIG
|
|
7
|
+
import { CO_VALUE_LOADING_CONFIG } from "../config.js";
|
|
8
8
|
import { coreToCoValue } from "../coreToCoValue.js";
|
|
9
9
|
import {
|
|
10
10
|
CryptoProvider,
|
|
@@ -380,7 +380,7 @@ export class CoValueCore {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
knownStateWithStreaming(): CoValueKnownState {
|
|
383
|
-
if (this.
|
|
383
|
+
if (this.verified) {
|
|
384
384
|
return this.verified.knownStateWithStreaming();
|
|
385
385
|
} else {
|
|
386
386
|
return emptyKnownState(this.id);
|
|
@@ -388,7 +388,7 @@ export class CoValueCore {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
knownState(): CoValueKnownState {
|
|
391
|
-
if (this.
|
|
391
|
+
if (this.verified) {
|
|
392
392
|
return this.verified.knownState();
|
|
393
393
|
} else {
|
|
394
394
|
return emptyKnownState(this.id);
|
|
@@ -605,8 +605,17 @@ export class CoValueCore {
|
|
|
605
605
|
)._unsafeUnwrap({ withStackTrace: true });
|
|
606
606
|
|
|
607
607
|
if (success) {
|
|
608
|
+
const session = this.verified.sessions.get(sessionID);
|
|
609
|
+
const txIdx = session ? session.transactions.length - 1 : 0;
|
|
610
|
+
|
|
608
611
|
this.node.syncManager.recordTransactionsSize([transaction], "local");
|
|
609
|
-
|
|
612
|
+
this.node.syncManager.syncLocalTransaction(
|
|
613
|
+
this.verified,
|
|
614
|
+
transaction,
|
|
615
|
+
sessionID,
|
|
616
|
+
signature,
|
|
617
|
+
txIdx,
|
|
618
|
+
);
|
|
610
619
|
}
|
|
611
620
|
|
|
612
621
|
return success;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Result, err, ok } from "neverthrow";
|
|
2
2
|
import { AnyRawCoValue } from "../coValue.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContentMessage,
|
|
5
|
+
exceedsRecommendedSize,
|
|
6
|
+
getTransactionSize,
|
|
7
|
+
} from "../coValueContentMessage.js";
|
|
4
8
|
import {
|
|
5
9
|
CryptoProvider,
|
|
6
10
|
Encrypted,
|
|
@@ -14,7 +18,6 @@ import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
|
14
18
|
import { Stringified } from "../jsonStringify.js";
|
|
15
19
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
16
20
|
import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
17
|
-
import { getPriorityFromHeader } from "../priority.js";
|
|
18
21
|
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
19
22
|
import { InvalidHashError, InvalidSignatureError } from "./coValueCore.js";
|
|
20
23
|
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
@@ -151,6 +154,17 @@ export class VerifiedState {
|
|
|
151
154
|
return ok(true as const);
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
getLastSignatureCheckpoint(sessionID: SessionID): number {
|
|
158
|
+
const sessionLog = this.sessions.get(sessionID);
|
|
159
|
+
|
|
160
|
+
if (!sessionLog?.signatureAfter) return -1;
|
|
161
|
+
|
|
162
|
+
return Object.keys(sessionLog.signatureAfter).reduce(
|
|
163
|
+
(max, idx) => Math.max(max, parseInt(idx)),
|
|
164
|
+
-1,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
private doAddTransactions(
|
|
155
169
|
sessionID: SessionID,
|
|
156
170
|
newTransactions: Transaction[],
|
|
@@ -165,24 +179,14 @@ export class VerifiedState {
|
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
const signatureAfter = sessionLog?.signatureAfter ?? {};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
|
171
|
-
-1,
|
|
172
|
-
);
|
|
182
|
+
const lastInbetweenSignatureIdx =
|
|
183
|
+
this.getLastSignatureCheckpoint(sessionID);
|
|
173
184
|
|
|
174
185
|
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
175
186
|
.slice(lastInbetweenSignatureIdx + 1)
|
|
176
|
-
.reduce(
|
|
177
|
-
(sum, tx) =>
|
|
178
|
-
sum +
|
|
179
|
-
(tx.privacy === "private"
|
|
180
|
-
? tx.encryptedChanges.length
|
|
181
|
-
: tx.changes.length),
|
|
182
|
-
0,
|
|
183
|
-
);
|
|
187
|
+
.reduce((sum, tx) => sum + getTransactionSize(tx), 0);
|
|
184
188
|
|
|
185
|
-
if (sizeOfTxsSinceLastInbetweenSignature
|
|
189
|
+
if (exceedsRecommendedSize(sizeOfTxsSinceLastInbetweenSignature)) {
|
|
186
190
|
signatureAfter[transactions.length - 1] = newSignature;
|
|
187
191
|
}
|
|
188
192
|
|
|
@@ -242,13 +246,11 @@ export class VerifiedState {
|
|
|
242
246
|
return this._cachedNewContentSinceEmpty;
|
|
243
247
|
}
|
|
244
248
|
|
|
245
|
-
let currentPiece: NewContentMessage =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
new: {},
|
|
251
|
-
};
|
|
249
|
+
let currentPiece: NewContentMessage = createContentMessage(
|
|
250
|
+
this.id,
|
|
251
|
+
this.header,
|
|
252
|
+
!knownState?.header,
|
|
253
|
+
);
|
|
252
254
|
|
|
253
255
|
const pieces = [currentPiece];
|
|
254
256
|
|
|
@@ -299,25 +301,16 @@ export class VerifiedState {
|
|
|
299
301
|
const oldPieceSize = pieceSize;
|
|
300
302
|
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
301
303
|
const tx = log.transactions[txIdx]!;
|
|
302
|
-
pieceSize +=
|
|
303
|
-
tx.privacy === "private"
|
|
304
|
-
? tx.encryptedChanges.length
|
|
305
|
-
: tx.changes.length;
|
|
304
|
+
pieceSize += getTransactionSize(tx);
|
|
306
305
|
}
|
|
307
306
|
|
|
308
|
-
if (pieceSize
|
|
307
|
+
if (exceedsRecommendedSize(pieceSize)) {
|
|
309
308
|
if (!currentPiece.expectContentUntil && pieces.length === 1) {
|
|
310
309
|
currentPiece.expectContentUntil =
|
|
311
310
|
this.knownStateWithStreaming().sessions;
|
|
312
311
|
}
|
|
313
312
|
|
|
314
|
-
currentPiece =
|
|
315
|
-
action: "content",
|
|
316
|
-
id: this.id,
|
|
317
|
-
header: undefined,
|
|
318
|
-
new: {},
|
|
319
|
-
priority: getPriorityFromHeader(this.header),
|
|
320
|
-
};
|
|
313
|
+
currentPiece = createContentMessage(this.id, this.header, false);
|
|
321
314
|
pieces.push(currentPiece);
|
|
322
315
|
pieceSize = pieceSize - oldPieceSize;
|
|
323
316
|
}
|
package/src/coValues/group.ts
CHANGED
|
@@ -669,19 +669,30 @@ export class RawGroup<
|
|
|
669
669
|
|
|
670
670
|
/** Detect circular references in group inheritance */
|
|
671
671
|
isSelfExtension(parent: RawGroup) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
672
|
+
const checkedGroups = new Set<string>();
|
|
673
|
+
const queue = [parent];
|
|
675
674
|
|
|
676
|
-
|
|
675
|
+
while (true) {
|
|
676
|
+
const current = queue.pop();
|
|
677
677
|
|
|
678
|
-
|
|
679
|
-
|
|
678
|
+
if (!current) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (current.id === this.id) {
|
|
680
683
|
return true;
|
|
681
684
|
}
|
|
682
|
-
}
|
|
683
685
|
|
|
684
|
-
|
|
686
|
+
checkedGroups.add(current.id);
|
|
687
|
+
|
|
688
|
+
const parentGroups = current.getParentGroups();
|
|
689
|
+
|
|
690
|
+
for (const parent of parentGroups) {
|
|
691
|
+
if (!checkedGroups.has(parent.id)) {
|
|
692
|
+
queue.push(parent);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
685
696
|
}
|
|
686
697
|
|
|
687
698
|
extend(
|
|
@@ -700,8 +711,8 @@ export class RawGroup<
|
|
|
700
711
|
|
|
701
712
|
const value = role === "inherit" ? "extend" : role;
|
|
702
713
|
|
|
703
|
-
this.set(`parent_${parent.id}`, value, "trusting");
|
|
704
714
|
parent.set(`child_${this.id}`, "extend", "trusting");
|
|
715
|
+
this.set(`parent_${parent.id}`, value, "trusting");
|
|
705
716
|
|
|
706
717
|
if (
|
|
707
718
|
parent.myRole() !== "admin" &&
|
package/src/localNode.ts
CHANGED
|
@@ -351,7 +351,7 @@ export class LocalNode {
|
|
|
351
351
|
new VerifiedState(id, this.crypto, header, new Map()),
|
|
352
352
|
);
|
|
353
353
|
|
|
354
|
-
|
|
354
|
+
this.syncManager.syncHeader(coValue.verified);
|
|
355
355
|
|
|
356
356
|
return coValue;
|
|
357
357
|
}
|
|
@@ -738,9 +738,14 @@ export class LocalNode {
|
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
|
|
742
|
-
|
|
741
|
+
/**
|
|
742
|
+
* Closes all the peer connections, drains all the queues and closes the storage.
|
|
743
|
+
*
|
|
744
|
+
* @returns Promise of the current pending store operation, if any.
|
|
745
|
+
*/
|
|
746
|
+
gracefulShutdown(): Promise<unknown> | undefined {
|
|
743
747
|
this.syncManager.gracefulShutdown();
|
|
748
|
+
return this.storage?.close();
|
|
744
749
|
}
|
|
745
750
|
}
|
|
746
751
|
|
|
@@ -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
|
}
|