cojson 0.18.6 → 0.18.8
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 +18 -0
- package/dist/coValueContentMessage.d.ts +2 -0
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +7 -0
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +2 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +2 -4
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/branching.d.ts +31 -9
- package/dist/coValueCore/branching.d.ts.map +1 -1
- package/dist/coValueCore/branching.js +50 -100
- package/dist/coValueCore/branching.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +12 -8
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +93 -23
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +4 -2
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +6 -4
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +10 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +2 -2
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +8 -8
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +14 -1
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
- package/dist/crypto/PureJSCrypto.js +14 -6
- package/dist/crypto/PureJSCrypto.js.map +1 -1
- package/dist/exports.d.ts +3 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +2 -2
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +1 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +10 -2
- package/dist/localNode.js.map +1 -1
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/sync.d.ts +3 -3
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +29 -19
- package/dist/sync.js.map +1 -1
- package/dist/tests/branching.test.js +107 -9
- package/dist/tests/branching.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +45 -1
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/sync.content.test.d.ts +2 -0
- package/dist/tests/sync.content.test.d.ts.map +1 -0
- package/dist/tests/sync.content.test.js +120 -0
- package/dist/tests/sync.content.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +15 -2
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +1 -1
- package/dist/tests/sync.upload.test.js +2 -2
- package/package.json +2 -2
- package/src/coValueContentMessage.ts +13 -0
- package/src/coValueCore/SessionMap.ts +2 -2
- package/src/coValueCore/branching.ts +94 -149
- package/src/coValueCore/coValueCore.ts +121 -27
- package/src/coValueCore/verifiedState.ts +8 -0
- package/src/coValues/coList.ts +12 -1
- package/src/coValues/coMap.ts +10 -12
- package/src/coValues/group.ts +14 -1
- package/src/config.ts +9 -0
- package/src/crypto/PureJSCrypto.ts +25 -13
- package/src/exports.ts +7 -1
- package/src/localNode.ts +12 -2
- package/src/storage/storageAsync.ts +0 -1
- package/src/sync.ts +37 -33
- package/src/tests/branching.test.ts +158 -9
- package/src/tests/coValueCore.test.ts +62 -2
- package/src/tests/sync.content.test.ts +153 -0
- package/src/tests/sync.load.test.ts +19 -2
- package/src/tests/sync.storage.test.ts +1 -1
- package/src/tests/sync.upload.test.ts +2 -2
|
@@ -1,9 +1,41 @@
|
|
|
1
|
-
import type { CoValueCore
|
|
2
|
-
import type { RawCoID, SessionID
|
|
1
|
+
import type { CoValueCore } from "../exports.js";
|
|
2
|
+
import type { RawCoID, SessionID } from "../ids.js";
|
|
3
3
|
import { type AvailableCoValueCore, idforHeader } from "./coValueCore.js";
|
|
4
4
|
import type { CoValueHeader } from "./verifiedState.js";
|
|
5
5
|
import type { CoValueKnownState } from "../sync.js";
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Commit to identify the starting point of the branch
|
|
9
|
+
*
|
|
10
|
+
* In case of clonflicts, the first commit of this kind is considered the source of truth
|
|
11
|
+
*/
|
|
12
|
+
export type BranchStartCommit = {
|
|
13
|
+
from: CoValueKnownState["sessions"];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Commit that tracks a branch creation
|
|
18
|
+
*/
|
|
19
|
+
export type BranchPointerCommit = {
|
|
20
|
+
branch: string;
|
|
21
|
+
ownerId?: RawCoID;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Meta information attached to each merged transaction to retrieve the original transaction ID
|
|
26
|
+
*/
|
|
27
|
+
export type MergedTransactionMetadata = {
|
|
28
|
+
mi: number; // Transaction index and marker of a merge commit
|
|
29
|
+
s?: SessionID;
|
|
30
|
+
b?: RawCoID;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Merge commit located in a branch to track how many transactions have already been merged
|
|
35
|
+
*/
|
|
36
|
+
export type MergeCommit = {
|
|
37
|
+
merged: CoValueKnownState["sessions"];
|
|
38
|
+
};
|
|
7
39
|
|
|
8
40
|
export function getBranchHeader({
|
|
9
41
|
type,
|
|
@@ -49,30 +81,37 @@ export function getBranchId(
|
|
|
49
81
|
);
|
|
50
82
|
}
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
const header = coValue.verified.header;
|
|
54
|
-
|
|
55
|
-
// Group and account coValues can't have branches, so we return the source id
|
|
56
|
-
if (header.ruleset.type !== "ownedByGroup") {
|
|
57
|
-
return coValue.id;
|
|
58
|
-
}
|
|
84
|
+
const currentOwnerId = ownerId ?? getBranchOwnerId(coValue);
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
if (!currentOwnerId) {
|
|
87
|
+
return coValue.id;
|
|
61
88
|
}
|
|
62
89
|
|
|
63
90
|
const header = getBranchHeader({
|
|
64
91
|
type: coValue.verified.header.type,
|
|
65
92
|
branchName: name,
|
|
66
|
-
ownerId,
|
|
93
|
+
ownerId: currentOwnerId,
|
|
67
94
|
sourceId: coValue.id,
|
|
68
95
|
});
|
|
69
96
|
|
|
70
97
|
return idforHeader(header, coValue.node.crypto);
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
export
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
export function getBranchOwnerId(coValue: CoValueCore) {
|
|
101
|
+
if (!coValue.verified) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"CoValueCore: getBranchOwnerId called on coValue without verified state",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const header = coValue.verified.header;
|
|
108
|
+
|
|
109
|
+
if (header.ruleset.type !== "ownedByGroup") {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return header.ruleset.group;
|
|
114
|
+
}
|
|
76
115
|
|
|
77
116
|
/**
|
|
78
117
|
* Given a coValue, a branch name and an owner id, creates a new branch CoValue
|
|
@@ -88,32 +127,34 @@ export function createBranch(
|
|
|
88
127
|
);
|
|
89
128
|
}
|
|
90
129
|
|
|
91
|
-
|
|
92
|
-
const header = coValue.verified.header;
|
|
93
|
-
|
|
94
|
-
// Group and account coValues can't have branches, so we return the source coValue
|
|
95
|
-
if (header.ruleset.type !== "ownedByGroup") {
|
|
96
|
-
return coValue;
|
|
97
|
-
}
|
|
130
|
+
const branchOwnerId = ownerId ?? getBranchOwnerId(coValue);
|
|
98
131
|
|
|
99
|
-
|
|
132
|
+
if (!branchOwnerId) {
|
|
133
|
+
return coValue;
|
|
100
134
|
}
|
|
101
135
|
|
|
102
136
|
const header = getBranchHeader({
|
|
103
137
|
type: coValue.verified.header.type,
|
|
104
138
|
branchName: name,
|
|
105
|
-
ownerId,
|
|
139
|
+
ownerId: branchOwnerId,
|
|
106
140
|
sourceId: coValue.id,
|
|
107
141
|
});
|
|
108
142
|
|
|
109
|
-
const
|
|
143
|
+
const branch = coValue.node.createCoValue(header);
|
|
144
|
+
const sessions = { ...coValue.knownState().sessions };
|
|
110
145
|
|
|
111
146
|
// Create a branch commit to identify the starting point of the branch
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} satisfies
|
|
147
|
+
branch.makeTransaction([], "private", {
|
|
148
|
+
from: sessions,
|
|
149
|
+
} satisfies BranchStartCommit);
|
|
150
|
+
|
|
151
|
+
// Create a branch pointer, to identify that we created a branch
|
|
152
|
+
coValue.makeTransaction([], "private", {
|
|
153
|
+
branch: name,
|
|
154
|
+
ownerId,
|
|
155
|
+
} satisfies BranchPointerCommit);
|
|
115
156
|
|
|
116
|
-
return
|
|
157
|
+
return branch;
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
/**
|
|
@@ -141,15 +182,6 @@ export function getBranchSource(
|
|
|
141
182
|
return source;
|
|
142
183
|
}
|
|
143
184
|
|
|
144
|
-
export type MergeCommit = {
|
|
145
|
-
// The point where the branch was merged
|
|
146
|
-
merge: CoValueKnownState["sessions"];
|
|
147
|
-
// The id of the branch that was merged
|
|
148
|
-
id: RawCoID;
|
|
149
|
-
// The number of transactions that were merged, will be used in the future to handle the edits history properly
|
|
150
|
-
count: number;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
185
|
/**
|
|
154
186
|
* Given a branch coValue, merges the branch into the source coValue
|
|
155
187
|
*/
|
|
@@ -164,12 +196,6 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
|
|
|
164
196
|
return branch;
|
|
165
197
|
}
|
|
166
198
|
|
|
167
|
-
const sourceId = branch.getCurrentBranchSourceId();
|
|
168
|
-
|
|
169
|
-
if (!sourceId) {
|
|
170
|
-
throw new Error("CoValueCore: mergeBranch called on a non-branch coValue");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
199
|
const target = getBranchSource(branch);
|
|
174
200
|
|
|
175
201
|
if (!target) {
|
|
@@ -178,13 +204,9 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
|
|
|
178
204
|
|
|
179
205
|
// Look for previous merge commits, to see which transactions needs to be merged
|
|
180
206
|
// Done mostly for performance reasons, as we could merge all the transactions every time and nothing would change
|
|
181
|
-
const mergedTransactions =
|
|
182
|
-
(acc, {
|
|
183
|
-
|
|
184
|
-
return acc;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (const [sessionID, count] of Object.entries(commit.merge) as [
|
|
207
|
+
const mergedTransactions = branch.getMergeCommits().reduce(
|
|
208
|
+
(acc, { merged }) => {
|
|
209
|
+
for (const [sessionID, count] of Object.entries(merged) as [
|
|
188
210
|
SessionID,
|
|
189
211
|
number,
|
|
190
212
|
][]) {
|
|
@@ -210,111 +232,34 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
|
|
|
210
232
|
return target;
|
|
211
233
|
}
|
|
212
234
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} satisfies MergeCommit);
|
|
219
|
-
|
|
220
|
-
const currentSessionID = target.node.currentSessionID;
|
|
235
|
+
// We do track in the meta information the original txID to make sure that
|
|
236
|
+
// the CoList opid still point to the correct transaction
|
|
237
|
+
// To reduce the cost of the meta we skip the repeated information
|
|
238
|
+
let lastSessionId: string | undefined = undefined;
|
|
239
|
+
let lastBranchId: string | undefined = undefined;
|
|
221
240
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const mapping: Record<`${SessionID}:${number}`, number> = {};
|
|
241
|
+
for (const tx of branchValidTransactions) {
|
|
242
|
+
const mergeMeta: MergedTransactionMetadata = {
|
|
243
|
+
mi: tx.txID.txIndex,
|
|
244
|
+
};
|
|
227
245
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
// Create a mapping from the branch transactions to the target transactions
|
|
232
|
-
for (const { txID } of branchValidTransactions) {
|
|
233
|
-
mapping[`${txID.sessionID}:${txID.txIndex}`] = txIdx;
|
|
234
|
-
txIdx++;
|
|
246
|
+
if (lastSessionId !== tx.txID.sessionID) {
|
|
247
|
+
mergeMeta.s = tx.txID.sessionID;
|
|
235
248
|
}
|
|
236
249
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
mapCoListChangesToTarget(
|
|
240
|
-
changes as ListOpPayload<JsonValue>[],
|
|
241
|
-
currentSessionID,
|
|
242
|
-
mapping,
|
|
243
|
-
),
|
|
244
|
-
tx.privacy,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
for (const { tx, changes } of branchValidTransactions) {
|
|
249
|
-
target.makeTransaction(changes, tx.privacy);
|
|
250
|
+
if (lastBranchId !== tx.txID.branch) {
|
|
251
|
+
mergeMeta.b = tx.txID.branch;
|
|
250
252
|
}
|
|
251
|
-
}
|
|
252
253
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Given a list of changes, maps the opIDs to the target transactions
|
|
258
|
-
*/
|
|
259
|
-
function mapCoListChangesToTarget(
|
|
260
|
-
changes: ListOpPayload<JsonValue>[],
|
|
261
|
-
currentSessionID: SessionID,
|
|
262
|
-
mapping: Record<`${SessionID}:${number}`, number>,
|
|
263
|
-
) {
|
|
264
|
-
return changes.map((change) => {
|
|
265
|
-
if (change.op === "app") {
|
|
266
|
-
if (change.after === "start") {
|
|
267
|
-
return change;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
...change,
|
|
272
|
-
after: convertOpID(change.after, currentSessionID, mapping),
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (change.op === "del") {
|
|
277
|
-
return {
|
|
278
|
-
...change,
|
|
279
|
-
insertion: convertOpID(change.insertion, currentSessionID, mapping),
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (change.op === "pre") {
|
|
284
|
-
if (change.before === "end") {
|
|
285
|
-
return change;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
...change,
|
|
290
|
-
before: convertOpID(change.before, currentSessionID, mapping),
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return change;
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function convertOpID(
|
|
299
|
-
opID: OpID,
|
|
300
|
-
sessionID: SessionID,
|
|
301
|
-
mapping: Record<`${SessionID}:${number}`, number>,
|
|
302
|
-
) {
|
|
303
|
-
// If the opID comes from the source branch, we don't need to map it
|
|
304
|
-
if (!opID.branch) {
|
|
305
|
-
return opID;
|
|
254
|
+
target.makeTransaction(tx.changes, tx.tx.privacy, mergeMeta, tx.madeAt);
|
|
255
|
+
lastSessionId = tx.txID.sessionID;
|
|
256
|
+
lastBranchId = tx.txID.branch;
|
|
306
257
|
}
|
|
307
258
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return opID;
|
|
313
|
-
}
|
|
259
|
+
// Track the merged transactions for the branch, so future merges will know which transactions have already been merged
|
|
260
|
+
branch.makeTransaction([], "private", {
|
|
261
|
+
merged: branch.knownState().sessions,
|
|
262
|
+
} satisfies MergeCommit);
|
|
314
263
|
|
|
315
|
-
return
|
|
316
|
-
sessionID: sessionID,
|
|
317
|
-
txIndex: mappedIndex,
|
|
318
|
-
changeIdx: opID.changeIdx,
|
|
319
|
-
};
|
|
264
|
+
return target;
|
|
320
265
|
}
|
|
@@ -5,10 +5,10 @@ import type { RawCoValue } from "../coValue.js";
|
|
|
5
5
|
import type { ControlledAccountOrAgent } from "../coValues/account.js";
|
|
6
6
|
import type { RawGroup } from "../coValues/group.js";
|
|
7
7
|
import { CO_VALUE_LOADING_CONFIG } from "../config.js";
|
|
8
|
+
import { validateTxSizeLimitInBytes } from "../coValueContentMessage.js";
|
|
8
9
|
import { coreToCoValue } from "../coreToCoValue.js";
|
|
9
10
|
import {
|
|
10
11
|
CryptoProvider,
|
|
11
|
-
Encrypted,
|
|
12
12
|
Hash,
|
|
13
13
|
KeyID,
|
|
14
14
|
KeySecret,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
} from "../crypto/crypto.js";
|
|
18
18
|
import { AgentID, RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
19
19
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
20
|
-
import { parseJSON, safeParseJSON } from "../jsonStringify.js";
|
|
21
20
|
import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
|
|
22
21
|
import { logger } from "../logger.js";
|
|
23
22
|
import { determineValidTransactions } from "../permissions.js";
|
|
@@ -29,10 +28,14 @@ import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
|
|
|
29
28
|
import { SessionMap } from "./SessionMap.js";
|
|
30
29
|
import {
|
|
31
30
|
MergeCommit,
|
|
31
|
+
BranchPointerCommit,
|
|
32
|
+
MergedTransactionMetadata,
|
|
32
33
|
createBranch,
|
|
33
34
|
getBranchId,
|
|
35
|
+
getBranchOwnerId,
|
|
34
36
|
getBranchSource,
|
|
35
37
|
mergeBranch,
|
|
38
|
+
BranchStartCommit,
|
|
36
39
|
} from "./branching.js";
|
|
37
40
|
import { type RawAccountID } from "../coValues/account.js";
|
|
38
41
|
import { decodeTransactionChangesAndMeta } from "./decodeTransactionChangesAndMeta.js";
|
|
@@ -70,6 +73,9 @@ export type VerifiedTransaction = {
|
|
|
70
73
|
|
|
71
74
|
// True if the meta information has been parsed and loaded in the CoValueCore
|
|
72
75
|
hasMetaBeenParsed: boolean;
|
|
76
|
+
|
|
77
|
+
// The previous verified transaction for the same session
|
|
78
|
+
previous: VerifiedTransaction | undefined;
|
|
73
79
|
};
|
|
74
80
|
|
|
75
81
|
export type DecryptedTransaction = {
|
|
@@ -600,6 +606,7 @@ export class CoValueCore {
|
|
|
600
606
|
changes: JsonValue[],
|
|
601
607
|
privacy: "private" | "trusting",
|
|
602
608
|
meta?: JsonObject,
|
|
609
|
+
madeAt?: number,
|
|
603
610
|
): boolean {
|
|
604
611
|
if (!this.verified) {
|
|
605
612
|
throw new Error(
|
|
@@ -607,6 +614,8 @@ export class CoValueCore {
|
|
|
607
614
|
);
|
|
608
615
|
}
|
|
609
616
|
|
|
617
|
+
validateTxSizeLimitInBytes(changes);
|
|
618
|
+
|
|
610
619
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
611
620
|
const sessionID =
|
|
612
621
|
this.verified.header.meta?.type === "account"
|
|
@@ -634,6 +643,7 @@ export class CoValueCore {
|
|
|
634
643
|
keyID,
|
|
635
644
|
keySecret,
|
|
636
645
|
meta,
|
|
646
|
+
madeAt ?? Date.now(),
|
|
637
647
|
);
|
|
638
648
|
} else {
|
|
639
649
|
result = this.verified.makeNewTrustingTransaction(
|
|
@@ -641,6 +651,7 @@ export class CoValueCore {
|
|
|
641
651
|
signerAgent,
|
|
642
652
|
changes,
|
|
643
653
|
meta,
|
|
654
|
+
madeAt ?? Date.now(),
|
|
644
655
|
);
|
|
645
656
|
}
|
|
646
657
|
|
|
@@ -689,12 +700,13 @@ export class CoValueCore {
|
|
|
689
700
|
}
|
|
690
701
|
|
|
691
702
|
// The starting point of the branch, in case this CoValue is a branch
|
|
692
|
-
branchStart:
|
|
693
|
-
| { branch: CoValueKnownState["sessions"]; madeAt: number }
|
|
694
|
-
| undefined;
|
|
703
|
+
branchStart: { from: BranchStartCommit["from"]; madeAt: number } | undefined;
|
|
695
704
|
|
|
696
705
|
// The list of merge commits that have been made
|
|
697
|
-
mergeCommits:
|
|
706
|
+
mergeCommits: MergeCommit[] = [];
|
|
707
|
+
branches: BranchPointerCommit[] = [];
|
|
708
|
+
earliestTxMadeAt: number = Number.MAX_SAFE_INTEGER;
|
|
709
|
+
latestTxMadeAt: number = 0;
|
|
698
710
|
|
|
699
711
|
// Reset the parsed transactions and branches, to validate them again from scratch when the group is updated
|
|
700
712
|
resetParsedTransactions() {
|
|
@@ -710,6 +722,11 @@ export class CoValueCore {
|
|
|
710
722
|
verifiedTransactions: VerifiedTransaction[] = [];
|
|
711
723
|
private verifiedTransactionsKnownSessions: CoValueKnownState["sessions"] = {};
|
|
712
724
|
|
|
725
|
+
private lastVerifiedTransactionBySessionID: Record<
|
|
726
|
+
SessionID,
|
|
727
|
+
VerifiedTransaction
|
|
728
|
+
> = {};
|
|
729
|
+
|
|
713
730
|
/**
|
|
714
731
|
* Loads the new transaction from the SessionMap into verifiedTransactions as a VerifiedTransaction.
|
|
715
732
|
*
|
|
@@ -752,7 +769,7 @@ export class CoValueCore {
|
|
|
752
769
|
txIndex,
|
|
753
770
|
};
|
|
754
771
|
|
|
755
|
-
|
|
772
|
+
const verifiedTransaction = {
|
|
756
773
|
author: accountOrAgentIDfromSessionID(sessionID),
|
|
757
774
|
txID,
|
|
758
775
|
madeAt: tx.madeAt,
|
|
@@ -764,7 +781,20 @@ export class CoValueCore {
|
|
|
764
781
|
hasInvalidMeta: false,
|
|
765
782
|
hasMetaBeenParsed: false,
|
|
766
783
|
tx,
|
|
767
|
-
|
|
784
|
+
previous: this.lastVerifiedTransactionBySessionID[sessionID],
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
if (verifiedTransaction.madeAt > this.latestTxMadeAt) {
|
|
788
|
+
this.latestTxMadeAt = verifiedTransaction.madeAt;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (verifiedTransaction.madeAt < this.earliestTxMadeAt) {
|
|
792
|
+
this.earliestTxMadeAt = verifiedTransaction.madeAt;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
this.verifiedTransactions.push(verifiedTransaction);
|
|
796
|
+
this.lastVerifiedTransactionBySessionID[sessionID] =
|
|
797
|
+
verifiedTransaction;
|
|
768
798
|
});
|
|
769
799
|
|
|
770
800
|
this.verifiedTransactionsKnownSessions[sessionID] =
|
|
@@ -793,23 +823,54 @@ export class CoValueCore {
|
|
|
793
823
|
|
|
794
824
|
transaction.hasMetaBeenParsed = true;
|
|
795
825
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
826
|
+
// Branch related meta information
|
|
827
|
+
if (this.isBranch()) {
|
|
828
|
+
// Check if the transaction is a branch start
|
|
829
|
+
if ("from" in transaction.meta) {
|
|
830
|
+
if (!this.branchStart || transaction.madeAt < this.branchStart.madeAt) {
|
|
831
|
+
const commit = transaction.meta as BranchStartCommit;
|
|
832
|
+
|
|
833
|
+
this.branchStart = {
|
|
834
|
+
from: commit.from,
|
|
835
|
+
madeAt: transaction.madeAt,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Check if the transaction is a merged checkpoint for a branch
|
|
841
|
+
if ("merged" in transaction.meta) {
|
|
842
|
+
const mergeCommit = transaction.meta as MergeCommit;
|
|
843
|
+
this.mergeCommits.push(mergeCommit);
|
|
844
|
+
}
|
|
804
845
|
}
|
|
805
846
|
|
|
806
|
-
if
|
|
807
|
-
|
|
847
|
+
// Check if the transaction is a branch pointer
|
|
848
|
+
if ("branch" in transaction.meta) {
|
|
849
|
+
const branch = transaction.meta as BranchPointerCommit;
|
|
808
850
|
|
|
809
|
-
this.
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
851
|
+
this.branches.push(branch);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Check if the transaction has been merged from a branch
|
|
855
|
+
if ("mi" in transaction.meta) {
|
|
856
|
+
const meta = transaction.meta as MergedTransactionMetadata;
|
|
857
|
+
|
|
858
|
+
// Check if the transaction is a merge commit
|
|
859
|
+
const previousTransaction = transaction.previous?.txID;
|
|
860
|
+
const sessionID = meta.s ?? previousTransaction?.sessionID;
|
|
861
|
+
|
|
862
|
+
if (sessionID) {
|
|
863
|
+
transaction.txID = {
|
|
864
|
+
sessionID,
|
|
865
|
+
txIndex: meta.mi,
|
|
866
|
+
branch: meta.b ?? previousTransaction?.branch,
|
|
867
|
+
};
|
|
868
|
+
} else {
|
|
869
|
+
logger.error("Merge commit without session ID", {
|
|
870
|
+
txID: transaction.txID,
|
|
871
|
+
prev: previousTransaction ?? null,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
813
874
|
}
|
|
814
875
|
}
|
|
815
876
|
|
|
@@ -876,7 +937,9 @@ export class CoValueCore {
|
|
|
876
937
|
const { txID } = transaction;
|
|
877
938
|
|
|
878
939
|
const from = options?.from?.[txID.sessionID] ?? -1;
|
|
879
|
-
|
|
940
|
+
|
|
941
|
+
// Load the to filter index. Sessions that are not in the to filter will be skipped
|
|
942
|
+
const to = options?.to ? (options.to[txID.sessionID] ?? -1) : Infinity;
|
|
880
943
|
|
|
881
944
|
// The txIndex starts at 0 and from/to are referring to the count of transactions
|
|
882
945
|
if (from > txID.txIndex || to < txID.txIndex) {
|
|
@@ -889,7 +952,7 @@ export class CoValueCore {
|
|
|
889
952
|
// If this is a branch, we load the valid transactions from the source
|
|
890
953
|
if (source && this.branchStart && !options?.skipBranchSource) {
|
|
891
954
|
const sourceTransactions = source.getValidTransactions({
|
|
892
|
-
to: this.branchStart.
|
|
955
|
+
to: this.branchStart.from,
|
|
893
956
|
ignorePrivateTransactions: options?.ignorePrivateTransactions ?? false,
|
|
894
957
|
knownTransactions: options?.knownTransactions,
|
|
895
958
|
});
|
|
@@ -915,15 +978,46 @@ export class CoValueCore {
|
|
|
915
978
|
}
|
|
916
979
|
|
|
917
980
|
getCurrentBranchName() {
|
|
918
|
-
return this.verified?.
|
|
981
|
+
return this.verified?.branchName;
|
|
919
982
|
}
|
|
920
983
|
|
|
921
984
|
getCurrentBranchSourceId() {
|
|
922
|
-
return this.verified?.
|
|
985
|
+
return this.verified?.branchSourceId;
|
|
923
986
|
}
|
|
924
987
|
|
|
925
988
|
isBranch() {
|
|
926
|
-
return Boolean(this.
|
|
989
|
+
return Boolean(this.verified?.branchSourceId);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
hasBranch(name: string, ownerId?: RawCoID) {
|
|
993
|
+
// This function requires the meta information to be parsed, which might not be the case
|
|
994
|
+
// if the value content hasn't been loaded yet
|
|
995
|
+
this.parseNewTransactions(false);
|
|
996
|
+
|
|
997
|
+
const currentOwnerId = getBranchOwnerId(this);
|
|
998
|
+
return this.branches.some((item) => {
|
|
999
|
+
if (item.branch !== name) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (item.ownerId === ownerId) {
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (!ownerId) {
|
|
1008
|
+
return item.ownerId === currentOwnerId;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (!item.ownerId) {
|
|
1012
|
+
return ownerId === currentOwnerId;
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
getMergeCommits() {
|
|
1018
|
+
this.parseNewTransactions(false);
|
|
1019
|
+
|
|
1020
|
+
return this.mergeCommits;
|
|
927
1021
|
}
|
|
928
1022
|
|
|
929
1023
|
getValidSortedTransactions(options?: {
|
|
@@ -59,6 +59,8 @@ export class VerifiedState {
|
|
|
59
59
|
private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
|
|
60
60
|
private streamingKnownState?: CoValueKnownState["sessions"];
|
|
61
61
|
public lastAccessed: number | undefined;
|
|
62
|
+
public branchSourceId?: RawCoID;
|
|
63
|
+
public branchName?: string;
|
|
62
64
|
|
|
63
65
|
constructor(
|
|
64
66
|
id: RawCoID,
|
|
@@ -74,6 +76,8 @@ export class VerifiedState {
|
|
|
74
76
|
this.streamingKnownState = streamingKnownState
|
|
75
77
|
? { ...streamingKnownState }
|
|
76
78
|
: undefined;
|
|
79
|
+
this.branchSourceId = header.meta?.source as RawCoID | undefined;
|
|
80
|
+
this.branchName = header.meta?.branch as string | undefined;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
clone(): VerifiedState {
|
|
@@ -114,12 +118,14 @@ export class VerifiedState {
|
|
|
114
118
|
signerAgent: ControlledAccountOrAgent,
|
|
115
119
|
changes: JsonValue[],
|
|
116
120
|
meta: JsonObject | undefined,
|
|
121
|
+
madeAt: number,
|
|
117
122
|
) {
|
|
118
123
|
const result = this.sessions.makeNewTrustingTransaction(
|
|
119
124
|
sessionID,
|
|
120
125
|
signerAgent,
|
|
121
126
|
changes,
|
|
122
127
|
meta,
|
|
128
|
+
madeAt,
|
|
123
129
|
);
|
|
124
130
|
|
|
125
131
|
this._cachedNewContentSinceEmpty = undefined;
|
|
@@ -135,6 +141,7 @@ export class VerifiedState {
|
|
|
135
141
|
keyID: KeyID,
|
|
136
142
|
keySecret: KeySecret,
|
|
137
143
|
meta: JsonObject | undefined,
|
|
144
|
+
madeAt: number,
|
|
138
145
|
) {
|
|
139
146
|
const result = this.sessions.makeNewPrivateTransaction(
|
|
140
147
|
sessionID,
|
|
@@ -143,6 +150,7 @@ export class VerifiedState {
|
|
|
143
150
|
keyID,
|
|
144
151
|
keySecret,
|
|
145
152
|
meta,
|
|
153
|
+
madeAt,
|
|
146
154
|
);
|
|
147
155
|
|
|
148
156
|
this._cachedNewContentSinceEmpty = undefined;
|
package/src/coValues/coList.ts
CHANGED
|
@@ -138,7 +138,13 @@ export class RawCoList<
|
|
|
138
138
|
sessionEntry[opID.txIndex] = txEntry;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Check if the change index already exists, may be the case of double merges
|
|
142
|
+
if (txEntry[opID.changeIdx]) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
141
146
|
txEntry[opID.changeIdx] = value;
|
|
147
|
+
return true;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
private isDeleted(opID: OpID) {
|
|
@@ -216,13 +222,18 @@ export class RawCoList<
|
|
|
216
222
|
};
|
|
217
223
|
|
|
218
224
|
if (change.op === "pre" || change.op === "app") {
|
|
219
|
-
this.createInsertionsEntry(opID, {
|
|
225
|
+
const created = this.createInsertionsEntry(opID, {
|
|
220
226
|
madeAt,
|
|
221
227
|
predecessors: [],
|
|
222
228
|
successors: [],
|
|
223
229
|
change,
|
|
224
230
|
});
|
|
225
231
|
|
|
232
|
+
// If the change index already exists, we don't need to process it again
|
|
233
|
+
if (!created) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
226
237
|
if (change.op === "pre") {
|
|
227
238
|
if (change.before === "end") {
|
|
228
239
|
this.beforeEnd.push(opID);
|