cojson 0.13.16 → 0.13.18
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 +16 -0
- package/dist/PeerState.d.ts +3 -0
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +9 -0
- package/dist/PeerState.js.map +1 -1
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -3
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +6 -4
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js +5 -4
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +143 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -0
- package/dist/{coValueCore.js → coValueCore/coValueCore.js} +314 -246
- package/dist/coValueCore/coValueCore.js.map +1 -0
- package/dist/coValueCore/verifiedState.d.ts +65 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -0
- package/dist/coValueCore/verifiedState.js +210 -0
- package/dist/coValueCore/verifiedState.js.map +1 -0
- package/dist/coValues/account.d.ts +8 -10
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +12 -13
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.d.ts +10 -6
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +41 -15
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +4 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +5 -3
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +2 -2
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +5 -5
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +5 -4
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +5 -3
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.d.ts +7 -2
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +29 -26
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.d.ts +4 -3
- package/dist/coreToCoValue.d.ts.map +1 -1
- package/dist/coreToCoValue.js +10 -14
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/exports.d.ts +6 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -4
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +30 -24
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +153 -177
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +15 -11
- package/dist/permissions.js.map +1 -1
- package/dist/priority.d.ts +1 -1
- package/dist/priority.d.ts.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +86 -55
- package/dist/sync.js.map +1 -1
- package/dist/tests/coList.test.js +133 -13
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +43 -14
- package/dist/tests/coMap.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +9 -10
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +49 -18
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +22 -28
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
- package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
- package/dist/tests/coValueCoreLoadingState.test.js +227 -0
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
- package/dist/tests/group.test.js +42 -43
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +2 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/permissions.test.js +224 -292
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/priority.test.js +13 -14
- package/dist/tests/priority.test.js.map +1 -1
- package/dist/tests/sync.auth.test.d.ts +2 -0
- package/dist/tests/sync.auth.test.d.ts.map +1 -0
- package/dist/tests/sync.auth.test.js +141 -0
- package/dist/tests/sync.auth.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +60 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +70 -10
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +19 -19
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +20 -13
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +32 -39
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +126 -37
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +24 -15
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +88 -61
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/expectGroup.js +1 -1
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +11 -0
- package/src/SyncStateManager.ts +2 -3
- package/src/coValue.ts +14 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +470 -413
- package/src/coValueCore/verifiedState.ts +376 -0
- package/src/coValues/account.ts +20 -25
- package/src/coValues/coList.ts +63 -29
- package/src/coValues/coMap.ts +13 -6
- package/src/coValues/coPlainText.ts +10 -8
- package/src/coValues/coStream.ts +12 -7
- package/src/coValues/group.ts +50 -28
- package/src/coreToCoValue.ts +14 -15
- package/src/exports.ts +9 -7
- package/src/localNode.ts +248 -283
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/sync.ts +96 -63
- package/src/tests/coList.test.ts +200 -12
- package/src/tests/coMap.test.ts +65 -14
- package/src/tests/coPlainText.test.ts +12 -9
- package/src/tests/coStream.test.ts +80 -17
- package/src/tests/coValueCore.test.ts +30 -27
- package/src/tests/coValueCoreLoadingState.test.ts +337 -0
- package/src/tests/group.test.ts +44 -68
- package/src/tests/messagesTestUtils.ts +3 -8
- package/src/tests/permissions.test.ts +283 -449
- package/src/tests/priority.test.ts +17 -13
- package/src/tests/sync.auth.test.ts +188 -0
- package/src/tests/sync.load.test.ts +79 -2
- package/src/tests/sync.mesh.test.ts +89 -9
- package/src/tests/sync.peerReconciliation.test.ts +25 -25
- package/src/tests/sync.storage.test.ts +20 -13
- package/src/tests/sync.test.ts +43 -43
- package/src/tests/sync.upload.test.ts +157 -37
- package/src/tests/testUtils.ts +120 -74
- package/src/typeUtils/expectGroup.ts +1 -1
- package/dist/CoValuesStore.d.ts +0 -14
- package/dist/CoValuesStore.d.ts.map +0 -1
- package/dist/CoValuesStore.js +0 -32
- package/dist/CoValuesStore.js.map +0 -1
- package/dist/coValueCore.d.ts +0 -141
- package/dist/coValueCore.d.ts.map +0 -1
- package/dist/coValueCore.js.map +0 -1
- package/dist/coValueState.d.ts +0 -34
- package/dist/coValueState.d.ts.map +0 -1
- package/dist/coValueState.js +0 -228
- package/dist/coValueState.js.map +0 -1
- package/dist/tests/coValueState.test.d.ts +0 -2
- package/dist/tests/coValueState.test.d.ts.map +0 -1
- package/dist/tests/coValueState.test.js +0 -344
- package/dist/tests/coValueState.test.js.map +0 -1
- package/src/CoValuesStore.ts +0 -41
- package/src/coValueState.ts +0 -300
- package/src/tests/coValueState.test.ts +0 -525
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { Result, err, ok } from "neverthrow";
|
|
2
|
+
import { AnyRawCoValue } from "../coValue.js";
|
|
3
|
+
import {
|
|
4
|
+
CryptoProvider,
|
|
5
|
+
Encrypted,
|
|
6
|
+
Hash,
|
|
7
|
+
KeyID,
|
|
8
|
+
Signature,
|
|
9
|
+
SignerID,
|
|
10
|
+
StreamingHash,
|
|
11
|
+
} from "../crypto/crypto.js";
|
|
12
|
+
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
13
|
+
import { Stringified } from "../jsonStringify.js";
|
|
14
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
15
|
+
import { PermissionsDef as RulesetDef } from "../permissions.js";
|
|
16
|
+
import { getPriorityFromHeader } from "../priority.js";
|
|
17
|
+
import { CoValueKnownState, NewContentMessage } from "../sync.js";
|
|
18
|
+
import {
|
|
19
|
+
InvalidHashError,
|
|
20
|
+
InvalidSignatureError,
|
|
21
|
+
MAX_RECOMMENDED_TX_SIZE,
|
|
22
|
+
} from "./coValueCore.js";
|
|
23
|
+
import { TryAddTransactionsError } from "./coValueCore.js";
|
|
24
|
+
|
|
25
|
+
export type CoValueHeader = {
|
|
26
|
+
type: AnyRawCoValue["type"];
|
|
27
|
+
ruleset: RulesetDef;
|
|
28
|
+
meta: JsonObject | null;
|
|
29
|
+
} & CoValueUniqueness;
|
|
30
|
+
|
|
31
|
+
export type CoValueUniqueness = {
|
|
32
|
+
uniqueness: JsonValue;
|
|
33
|
+
createdAt?: `2${string}` | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type PrivateTransaction = {
|
|
37
|
+
privacy: "private";
|
|
38
|
+
madeAt: number;
|
|
39
|
+
keyUsed: KeyID;
|
|
40
|
+
encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type TrustingTransaction = {
|
|
44
|
+
privacy: "trusting";
|
|
45
|
+
madeAt: number;
|
|
46
|
+
changes: Stringified<JsonValue[]>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type Transaction = PrivateTransaction | TrustingTransaction;
|
|
50
|
+
|
|
51
|
+
type SessionLog = {
|
|
52
|
+
readonly transactions: Transaction[];
|
|
53
|
+
lastHash?: Hash;
|
|
54
|
+
streamingHash: StreamingHash;
|
|
55
|
+
readonly signatureAfter: { [txIdx: number]: Signature | undefined };
|
|
56
|
+
lastSignature: Signature;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ValidatedSessions = Map<SessionID, SessionLog>;
|
|
60
|
+
|
|
61
|
+
export class VerifiedState {
|
|
62
|
+
readonly id: RawCoID;
|
|
63
|
+
readonly crypto: CryptoProvider;
|
|
64
|
+
readonly header: CoValueHeader;
|
|
65
|
+
readonly sessions: ValidatedSessions;
|
|
66
|
+
private _cachedKnownState?: CoValueKnownState;
|
|
67
|
+
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
id: RawCoID,
|
|
71
|
+
crypto: CryptoProvider,
|
|
72
|
+
header: CoValueHeader,
|
|
73
|
+
sessions: ValidatedSessions,
|
|
74
|
+
) {
|
|
75
|
+
this.id = id;
|
|
76
|
+
this.crypto = crypto;
|
|
77
|
+
this.header = header;
|
|
78
|
+
this.sessions = sessions;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
clone(): VerifiedState {
|
|
82
|
+
// do a deep clone, including the sessions
|
|
83
|
+
const clonedSessions = new Map();
|
|
84
|
+
for (let [sessionID, sessionLog] of this.sessions) {
|
|
85
|
+
clonedSessions.set(sessionID, {
|
|
86
|
+
lastSignature: sessionLog.lastSignature,
|
|
87
|
+
lastHash: sessionLog.lastHash,
|
|
88
|
+
streamingHash: sessionLog.streamingHash.clone(),
|
|
89
|
+
signatureAfter: { ...sessionLog.signatureAfter },
|
|
90
|
+
transactions: sessionLog.transactions.slice(),
|
|
91
|
+
} satisfies SessionLog);
|
|
92
|
+
}
|
|
93
|
+
return new VerifiedState(this.id, this.crypto, this.header, clonedSessions);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
tryAddTransactions(
|
|
97
|
+
sessionID: SessionID,
|
|
98
|
+
signerID: SignerID,
|
|
99
|
+
newTransactions: Transaction[],
|
|
100
|
+
givenExpectedNewHash: Hash | undefined,
|
|
101
|
+
newSignature: Signature,
|
|
102
|
+
skipVerify: boolean = false,
|
|
103
|
+
givenNewStreamingHash?: StreamingHash,
|
|
104
|
+
): Result<true, TryAddTransactionsError> {
|
|
105
|
+
if (skipVerify === true && givenNewStreamingHash && givenExpectedNewHash) {
|
|
106
|
+
this.doAddTransactions(
|
|
107
|
+
sessionID,
|
|
108
|
+
newTransactions,
|
|
109
|
+
newSignature,
|
|
110
|
+
givenExpectedNewHash,
|
|
111
|
+
givenNewStreamingHash,
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
|
|
115
|
+
sessionID,
|
|
116
|
+
newTransactions,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
|
|
120
|
+
return err({
|
|
121
|
+
type: "InvalidHash",
|
|
122
|
+
id: this.id,
|
|
123
|
+
expectedNewHash,
|
|
124
|
+
givenExpectedNewHash,
|
|
125
|
+
} satisfies InvalidHashError);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
|
|
129
|
+
return err({
|
|
130
|
+
type: "InvalidSignature",
|
|
131
|
+
id: this.id,
|
|
132
|
+
newSignature,
|
|
133
|
+
sessionID,
|
|
134
|
+
signerID,
|
|
135
|
+
} satisfies InvalidSignatureError);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.doAddTransactions(
|
|
139
|
+
sessionID,
|
|
140
|
+
newTransactions,
|
|
141
|
+
newSignature,
|
|
142
|
+
expectedNewHash,
|
|
143
|
+
newStreamingHash,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return ok(true as const);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private doAddTransactions(
|
|
151
|
+
sessionID: SessionID,
|
|
152
|
+
newTransactions: Transaction[],
|
|
153
|
+
newSignature: Signature,
|
|
154
|
+
expectedNewHash: Hash,
|
|
155
|
+
newStreamingHash: StreamingHash,
|
|
156
|
+
) {
|
|
157
|
+
const transactions = this.sessions.get(sessionID)?.transactions ?? [];
|
|
158
|
+
|
|
159
|
+
for (const tx of newTransactions) {
|
|
160
|
+
transactions.push(tx);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const signatureAfter = this.sessions.get(sessionID)?.signatureAfter ?? {};
|
|
164
|
+
|
|
165
|
+
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
|
|
166
|
+
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
|
167
|
+
-1,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
171
|
+
.slice(lastInbetweenSignatureIdx + 1)
|
|
172
|
+
.reduce(
|
|
173
|
+
(sum, tx) =>
|
|
174
|
+
sum +
|
|
175
|
+
(tx.privacy === "private"
|
|
176
|
+
? tx.encryptedChanges.length
|
|
177
|
+
: tx.changes.length),
|
|
178
|
+
0,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
182
|
+
signatureAfter[transactions.length - 1] = newSignature;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.sessions.set(sessionID, {
|
|
186
|
+
transactions,
|
|
187
|
+
lastHash: expectedNewHash,
|
|
188
|
+
streamingHash: newStreamingHash,
|
|
189
|
+
lastSignature: newSignature,
|
|
190
|
+
signatureAfter: signatureAfter,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
this._cachedNewContentSinceEmpty = undefined;
|
|
194
|
+
this._cachedKnownState = undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
expectedNewHashAfter(
|
|
198
|
+
sessionID: SessionID,
|
|
199
|
+
newTransactions: Transaction[],
|
|
200
|
+
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
|
201
|
+
const streamingHash =
|
|
202
|
+
this.sessions.get(sessionID)?.streamingHash.clone() ??
|
|
203
|
+
new StreamingHash(this.crypto);
|
|
204
|
+
|
|
205
|
+
for (const transaction of newTransactions) {
|
|
206
|
+
streamingHash.update(transaction);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
expectedNewHash: streamingHash.digest(),
|
|
211
|
+
newStreamingHash: streamingHash,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
newContentSince(
|
|
216
|
+
knownState: CoValueKnownState | undefined,
|
|
217
|
+
): NewContentMessage[] | undefined {
|
|
218
|
+
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
219
|
+
|
|
220
|
+
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
221
|
+
return this._cachedNewContentSinceEmpty;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let currentPiece: NewContentMessage = {
|
|
225
|
+
action: "content",
|
|
226
|
+
id: this.id,
|
|
227
|
+
header: knownState?.header ? undefined : this.header,
|
|
228
|
+
priority: getPriorityFromHeader(this.header),
|
|
229
|
+
new: {},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const pieces = [currentPiece];
|
|
233
|
+
|
|
234
|
+
const sentState: CoValueKnownState["sessions"] = {};
|
|
235
|
+
|
|
236
|
+
let pieceSize = 0;
|
|
237
|
+
|
|
238
|
+
let sessionsTodoAgain: Set<SessionID> | undefined | "first" = "first";
|
|
239
|
+
|
|
240
|
+
while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
|
|
241
|
+
if (sessionsTodoAgain === "first") {
|
|
242
|
+
sessionsTodoAgain = undefined;
|
|
243
|
+
}
|
|
244
|
+
const sessionsTodo = sessionsTodoAgain ?? this.sessions.keys();
|
|
245
|
+
|
|
246
|
+
for (const sessionIDKey of sessionsTodo) {
|
|
247
|
+
const sessionID = sessionIDKey as SessionID;
|
|
248
|
+
const log = this.sessions.get(sessionID)!;
|
|
249
|
+
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
250
|
+
const sentStateForSessionID = sentState[sessionID];
|
|
251
|
+
const nextKnownSignatureIdx = getNextKnownSignatureIdx(
|
|
252
|
+
log,
|
|
253
|
+
knownStateForSessionID,
|
|
254
|
+
sentStateForSessionID,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const firstNewTxIdx =
|
|
258
|
+
sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
259
|
+
const afterLastNewTxIdx =
|
|
260
|
+
nextKnownSignatureIdx === undefined
|
|
261
|
+
? log.transactions.length
|
|
262
|
+
: nextKnownSignatureIdx + 1;
|
|
263
|
+
|
|
264
|
+
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
265
|
+
|
|
266
|
+
if (nNewTx === 0) {
|
|
267
|
+
sessionsTodoAgain?.delete(sessionID);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (afterLastNewTxIdx < log.transactions.length) {
|
|
272
|
+
if (!sessionsTodoAgain) {
|
|
273
|
+
sessionsTodoAgain = new Set();
|
|
274
|
+
}
|
|
275
|
+
sessionsTodoAgain.add(sessionID);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const oldPieceSize = pieceSize;
|
|
279
|
+
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
280
|
+
const tx = log.transactions[txIdx]!;
|
|
281
|
+
pieceSize +=
|
|
282
|
+
tx.privacy === "private"
|
|
283
|
+
? tx.encryptedChanges.length
|
|
284
|
+
: tx.changes.length;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
288
|
+
currentPiece = {
|
|
289
|
+
action: "content",
|
|
290
|
+
id: this.id,
|
|
291
|
+
header: undefined,
|
|
292
|
+
new: {},
|
|
293
|
+
priority: getPriorityFromHeader(this.header),
|
|
294
|
+
};
|
|
295
|
+
pieces.push(currentPiece);
|
|
296
|
+
pieceSize = pieceSize - oldPieceSize;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let sessionEntry = currentPiece.new[sessionID];
|
|
300
|
+
if (!sessionEntry) {
|
|
301
|
+
sessionEntry = {
|
|
302
|
+
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
303
|
+
newTransactions: [],
|
|
304
|
+
lastSignature: "WILL_BE_REPLACED" as Signature,
|
|
305
|
+
};
|
|
306
|
+
currentPiece.new[sessionID] = sessionEntry;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
310
|
+
const tx = log.transactions[txIdx]!;
|
|
311
|
+
sessionEntry.newTransactions.push(tx);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
sessionEntry.lastSignature =
|
|
315
|
+
nextKnownSignatureIdx === undefined
|
|
316
|
+
? log.lastSignature!
|
|
317
|
+
: log.signatureAfter[nextKnownSignatureIdx]!;
|
|
318
|
+
|
|
319
|
+
sentState[sessionID] =
|
|
320
|
+
(sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const piecesWithContent = pieces.filter(
|
|
325
|
+
(piece) => Object.keys(piece.new).length > 0 || piece.header,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (piecesWithContent.length === 0) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (isKnownStateEmpty) {
|
|
333
|
+
this._cachedNewContentSinceEmpty = piecesWithContent;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return piecesWithContent;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
knownState(): CoValueKnownState {
|
|
340
|
+
if (this._cachedKnownState) {
|
|
341
|
+
return this._cachedKnownState;
|
|
342
|
+
} else {
|
|
343
|
+
const knownState = this.knownStateUncached();
|
|
344
|
+
this._cachedKnownState = knownState;
|
|
345
|
+
return knownState;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** @internal */
|
|
350
|
+
knownStateUncached(): CoValueKnownState {
|
|
351
|
+
const sessions: CoValueKnownState["sessions"] = {};
|
|
352
|
+
|
|
353
|
+
for (const [sessionID, sessionLog] of this.sessions.entries()) {
|
|
354
|
+
sessions[sessionID] = sessionLog.transactions.length;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
id: this.id,
|
|
359
|
+
header: true,
|
|
360
|
+
sessions,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function getNextKnownSignatureIdx(
|
|
366
|
+
log: SessionLog,
|
|
367
|
+
knownStateForSessionID?: number,
|
|
368
|
+
sentStateForSessionID?: number,
|
|
369
|
+
) {
|
|
370
|
+
return Object.keys(log.signatureAfter)
|
|
371
|
+
.map(Number)
|
|
372
|
+
.sort((a, b) => a - b)
|
|
373
|
+
.find(
|
|
374
|
+
(idx) => idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
|
|
375
|
+
);
|
|
376
|
+
}
|
package/src/coValues/account.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { CoID, RawCoValue } from "../coValue.js";
|
|
2
2
|
import {
|
|
3
|
+
AvailableCoValueCore,
|
|
3
4
|
CoValueCore,
|
|
5
|
+
} from "../coValueCore/coValueCore.js";
|
|
6
|
+
import {
|
|
4
7
|
CoValueHeader,
|
|
5
8
|
CoValueUniqueness,
|
|
6
|
-
} from "../coValueCore.js";
|
|
9
|
+
} from "../coValueCore/verifiedState.js";
|
|
7
10
|
import {
|
|
8
11
|
AgentSecret,
|
|
9
12
|
CryptoProvider,
|
|
@@ -85,35 +88,20 @@ export interface ControlledAccountOrAgent {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/** @hidden */
|
|
88
|
-
export class
|
|
89
|
-
|
|
90
|
-
implements ControlledAccountOrAgent
|
|
91
|
-
{
|
|
91
|
+
export class ControlledAccount implements ControlledAccountOrAgent {
|
|
92
|
+
account: RawAccount<AccountMeta>;
|
|
92
93
|
agentSecret: AgentSecret;
|
|
94
|
+
_cachedCurrentAgentID: AgentID | undefined;
|
|
93
95
|
crypto: CryptoProvider;
|
|
94
96
|
|
|
95
|
-
constructor(
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
constructor(account: RawAccount<AccountMeta>, agentSecret: AgentSecret) {
|
|
98
|
+
this.account = account;
|
|
98
99
|
this.agentSecret = agentSecret;
|
|
99
|
-
this.crypto = core.node.crypto;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Creates a new group (with the current account as the group's first admin).
|
|
104
|
-
* @category 1. High-level
|
|
105
|
-
*/
|
|
106
|
-
createGroup(
|
|
107
|
-
uniqueness: CoValueUniqueness = this.core.crypto.createdNowUnique(),
|
|
108
|
-
) {
|
|
109
|
-
return this.core.node.createGroup(uniqueness);
|
|
100
|
+
this.crypto = account.core.node.crypto;
|
|
110
101
|
}
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
inviteSecret: InviteSecret,
|
|
115
|
-
): Promise<void> {
|
|
116
|
-
return this.core.node.acceptInvite(groupOrOwnedValueID, inviteSecret);
|
|
103
|
+
get id(): RawAccountID {
|
|
104
|
+
return this.account.id;
|
|
117
105
|
}
|
|
118
106
|
|
|
119
107
|
currentAgentID(): AgentID {
|
|
@@ -186,7 +174,14 @@ export class RawProfile<
|
|
|
186
174
|
> extends RawCoMap<Shape, Meta> {}
|
|
187
175
|
|
|
188
176
|
export type RawAccountMigration<Meta extends AccountMeta = AccountMeta> = (
|
|
189
|
-
account:
|
|
177
|
+
account: RawAccount<Meta>,
|
|
190
178
|
localNode: LocalNode,
|
|
191
179
|
creationProps?: { name: string },
|
|
192
180
|
) => void | Promise<void>;
|
|
181
|
+
|
|
182
|
+
export function expectAccount(content: RawCoValue): RawAccount {
|
|
183
|
+
if (!(content instanceof RawAccount)) {
|
|
184
|
+
throw new Error("Expected an account");
|
|
185
|
+
}
|
|
186
|
+
return content;
|
|
187
|
+
}
|
package/src/coValues/coList.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { CoID, RawCoValue } from "../coValue.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AvailableCoValueCore,
|
|
4
|
+
CoValueCore,
|
|
5
|
+
} from "../coValueCore/coValueCore.js";
|
|
3
6
|
import { AgentID, SessionID, TransactionID } from "../ids.js";
|
|
4
7
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
8
|
+
import { CoValueKnownState } from "../sync.js";
|
|
5
9
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
6
10
|
import { isCoValue } from "../typeUtils/isCoValue.js";
|
|
7
11
|
import { RawAccountID } from "./account.js";
|
|
@@ -41,7 +45,7 @@ type DeletionEntry = {
|
|
|
41
45
|
deletionID: OpID;
|
|
42
46
|
} & DeletionOpPayload;
|
|
43
47
|
|
|
44
|
-
export class
|
|
48
|
+
export class RawCoList<
|
|
45
49
|
Item extends JsonValue = JsonValue,
|
|
46
50
|
Meta extends JsonObject | null = null,
|
|
47
51
|
> implements RawCoValue
|
|
@@ -51,7 +55,7 @@ export class RawCoListView<
|
|
|
51
55
|
/** @category 6. Meta */
|
|
52
56
|
type: "colist" | "coplaintext" = "colist" as const;
|
|
53
57
|
/** @category 6. Meta */
|
|
54
|
-
core:
|
|
58
|
+
core: AvailableCoValueCore;
|
|
55
59
|
/** @internal */
|
|
56
60
|
afterStart: OpID[];
|
|
57
61
|
/** @internal */
|
|
@@ -81,26 +85,52 @@ export class RawCoListView<
|
|
|
81
85
|
madeAt: number;
|
|
82
86
|
opID: OpID;
|
|
83
87
|
}[];
|
|
88
|
+
/** @internal */
|
|
89
|
+
totalValidTransactions = 0;
|
|
90
|
+
knownTransactions: CoValueKnownState["sessions"] = {};
|
|
91
|
+
lastValidTransaction: number | undefined;
|
|
84
92
|
|
|
85
93
|
/** @internal */
|
|
86
|
-
constructor(core:
|
|
94
|
+
constructor(core: AvailableCoValueCore) {
|
|
87
95
|
this.id = core.id as CoID<this>;
|
|
88
96
|
this.core = core;
|
|
89
|
-
this.afterStart = [];
|
|
90
|
-
this.beforeEnd = [];
|
|
91
|
-
this.insertions = {};
|
|
92
|
-
this.deletionsByInsertion = {};
|
|
93
97
|
|
|
94
98
|
this.insertions = {};
|
|
95
99
|
this.deletionsByInsertion = {};
|
|
96
100
|
this.afterStart = [];
|
|
97
101
|
this.beforeEnd = [];
|
|
102
|
+
this.knownTransactions = {};
|
|
103
|
+
|
|
104
|
+
this.processNewTransactions();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
processNewTransactions() {
|
|
108
|
+
const transactions = this.core.getValidSortedTransactions({
|
|
109
|
+
ignorePrivateTransactions: false,
|
|
110
|
+
knownTransactions: this.knownTransactions,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (transactions.length === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.totalValidTransactions += transactions.length;
|
|
118
|
+
let lastValidTransaction: number | undefined = undefined;
|
|
119
|
+
let oldestValidTransaction: number | undefined = undefined;
|
|
120
|
+
this._cachedEntries = undefined;
|
|
121
|
+
|
|
122
|
+
for (const { txID, changes, madeAt } of transactions) {
|
|
123
|
+
lastValidTransaction = Math.max(lastValidTransaction ?? 0, madeAt);
|
|
124
|
+
oldestValidTransaction = Math.min(
|
|
125
|
+
oldestValidTransaction ?? Infinity,
|
|
126
|
+
madeAt,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
this.knownTransactions[txID.sessionID] = Math.max(
|
|
130
|
+
this.knownTransactions[txID.sessionID] ?? 0,
|
|
131
|
+
txID.txIndex,
|
|
132
|
+
);
|
|
98
133
|
|
|
99
|
-
for (const {
|
|
100
|
-
txID,
|
|
101
|
-
changes,
|
|
102
|
-
madeAt,
|
|
103
|
-
} of this.core.getValidSortedTransactions()) {
|
|
104
134
|
for (const [changeIdx, changeUntyped] of changes.entries()) {
|
|
105
135
|
const change = changeUntyped as ListOpPayload<Item>;
|
|
106
136
|
|
|
@@ -193,11 +223,21 @@ export class RawCoListView<
|
|
|
193
223
|
}
|
|
194
224
|
}
|
|
195
225
|
}
|
|
226
|
+
|
|
227
|
+
if (
|
|
228
|
+
this.lastValidTransaction &&
|
|
229
|
+
oldestValidTransaction &&
|
|
230
|
+
oldestValidTransaction < this.lastValidTransaction
|
|
231
|
+
) {
|
|
232
|
+
this.rebuildFromCore();
|
|
233
|
+
} else {
|
|
234
|
+
this.lastValidTransaction = lastValidTransaction;
|
|
235
|
+
}
|
|
196
236
|
}
|
|
197
237
|
|
|
198
238
|
/** @category 6. Meta */
|
|
199
239
|
get headerMeta(): Meta {
|
|
200
|
-
return this.core.header.meta as Meta;
|
|
240
|
+
return this.core.verified.header.meta as Meta;
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
/** @category 6. Meta */
|
|
@@ -403,19 +443,11 @@ export class RawCoListView<
|
|
|
403
443
|
|
|
404
444
|
/** @category 3. Subscription */
|
|
405
445
|
subscribe(listener: (coList: this) => void): () => void {
|
|
406
|
-
return this.core.subscribe((
|
|
407
|
-
listener(
|
|
446
|
+
return this.core.subscribe((core) => {
|
|
447
|
+
listener(core.getCurrentContent() as this);
|
|
408
448
|
});
|
|
409
449
|
}
|
|
410
|
-
}
|
|
411
450
|
|
|
412
|
-
export class RawCoList<
|
|
413
|
-
Item extends JsonValue = JsonValue,
|
|
414
|
-
Meta extends JsonObject | null = JsonObject | null,
|
|
415
|
-
>
|
|
416
|
-
extends RawCoListView<Item, Meta>
|
|
417
|
-
implements RawCoValue
|
|
418
|
-
{
|
|
419
451
|
/** Appends `item` after the item currently at index `after`.
|
|
420
452
|
*
|
|
421
453
|
* If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
|
|
@@ -480,8 +512,7 @@ export class RawCoList<
|
|
|
480
512
|
}
|
|
481
513
|
|
|
482
514
|
this.core.makeTransaction(changes, privacy);
|
|
483
|
-
|
|
484
|
-
this.rebuildFromCore();
|
|
515
|
+
this.processNewTransactions();
|
|
485
516
|
}
|
|
486
517
|
|
|
487
518
|
/**
|
|
@@ -528,7 +559,7 @@ export class RawCoList<
|
|
|
528
559
|
privacy,
|
|
529
560
|
);
|
|
530
561
|
|
|
531
|
-
this.
|
|
562
|
+
this.processNewTransactions();
|
|
532
563
|
}
|
|
533
564
|
|
|
534
565
|
/** Deletes the item at index `at`.
|
|
@@ -555,7 +586,7 @@ export class RawCoList<
|
|
|
555
586
|
privacy,
|
|
556
587
|
);
|
|
557
588
|
|
|
558
|
-
this.
|
|
589
|
+
this.processNewTransactions();
|
|
559
590
|
}
|
|
560
591
|
|
|
561
592
|
replace(
|
|
@@ -583,7 +614,7 @@ export class RawCoList<
|
|
|
583
614
|
],
|
|
584
615
|
privacy,
|
|
585
616
|
);
|
|
586
|
-
this.
|
|
617
|
+
this.processNewTransactions();
|
|
587
618
|
}
|
|
588
619
|
|
|
589
620
|
/** @internal */
|
|
@@ -593,6 +624,9 @@ export class RawCoList<
|
|
|
593
624
|
this.afterStart = listAfter.afterStart;
|
|
594
625
|
this.beforeEnd = listAfter.beforeEnd;
|
|
595
626
|
this.insertions = listAfter.insertions;
|
|
627
|
+
this.totalValidTransactions = listAfter.totalValidTransactions;
|
|
628
|
+
this.lastValidTransaction = listAfter.lastValidTransaction;
|
|
629
|
+
this.knownTransactions = listAfter.knownTransactions;
|
|
596
630
|
this.deletionsByInsertion = listAfter.deletionsByInsertion;
|
|
597
631
|
this._cachedEntries = undefined;
|
|
598
632
|
}
|
package/src/coValues/coMap.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CoID, RawCoValue } from "../coValue.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AvailableCoValueCore,
|
|
4
|
+
CoValueCore,
|
|
5
|
+
} from "../coValueCore/coValueCore.js";
|
|
3
6
|
import { AgentID, TransactionID } from "../ids.js";
|
|
4
7
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
5
8
|
import { CoValueKnownState } from "../sync.js";
|
|
@@ -39,7 +42,7 @@ export class RawCoMapView<
|
|
|
39
42
|
/** @category 6. Meta */
|
|
40
43
|
type = "comap" as const;
|
|
41
44
|
/** @category 6. Meta */
|
|
42
|
-
core:
|
|
45
|
+
core: AvailableCoValueCore;
|
|
43
46
|
/** @internal */
|
|
44
47
|
latest: {
|
|
45
48
|
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>;
|
|
@@ -60,9 +63,11 @@ export class RawCoMapView<
|
|
|
60
63
|
/** @category 6. Meta */
|
|
61
64
|
readonly _shape!: Shape;
|
|
62
65
|
|
|
66
|
+
totalValidTransactions = 0;
|
|
67
|
+
|
|
63
68
|
/** @internal */
|
|
64
69
|
constructor(
|
|
65
|
-
core:
|
|
70
|
+
core: AvailableCoValueCore,
|
|
66
71
|
options?: {
|
|
67
72
|
ignorePrivateTransactions: boolean;
|
|
68
73
|
},
|
|
@@ -133,6 +138,8 @@ export class RawCoMapView<
|
|
|
133
138
|
}
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
this.totalValidTransactions += newValidTransactions.length;
|
|
142
|
+
|
|
136
143
|
for (const entries of changedEntries.values()) {
|
|
137
144
|
entries.sort(this.core.compareTransactions);
|
|
138
145
|
}
|
|
@@ -148,7 +155,7 @@ export class RawCoMapView<
|
|
|
148
155
|
|
|
149
156
|
/** @category 6. Meta */
|
|
150
157
|
get headerMeta(): Meta {
|
|
151
|
-
return this.core.header.meta as Meta;
|
|
158
|
+
return this.core.verified.header.meta as Meta;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
/** @category 6. Meta */
|
|
@@ -340,8 +347,8 @@ export class RawCoMapView<
|
|
|
340
347
|
|
|
341
348
|
/** @category 3. Subscription */
|
|
342
349
|
subscribe(listener: (coMap: this) => void): () => void {
|
|
343
|
-
return this.core.subscribe((
|
|
344
|
-
listener(
|
|
350
|
+
return this.core.subscribe((core) => {
|
|
351
|
+
listener(core.getCurrentContent() as this);
|
|
345
352
|
});
|
|
346
353
|
}
|
|
347
354
|
}
|