cojson 0.17.14 → 0.18.1
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 +34 -0
- package/dist/coValueCore/branching.d.ts +36 -0
- package/dist/coValueCore/branching.d.ts.map +1 -0
- package/dist/coValueCore/branching.js +122 -0
- package/dist/coValueCore/branching.js.map +1 -0
- package/dist/coValueCore/coValueCore.d.ts +71 -5
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +162 -53
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +3 -0
- package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +1 -0
- package/dist/coValueCore/decodeTransactionChangesAndMeta.js +59 -0
- package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +1 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValues/account.d.ts +2 -1
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +6 -0
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -3
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +4 -7
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +6 -6
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.d.ts +3 -3
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +4 -4
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/jsonStringify.d.ts +1 -0
- package/dist/jsonStringify.d.ts.map +1 -1
- package/dist/jsonStringify.js +8 -0
- package/dist/jsonStringify.js.map +1 -1
- package/dist/permissions.d.ts +2 -7
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +72 -70
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts +2 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +16 -5
- package/dist/sync.js.map +1 -1
- package/dist/tests/account.test.js +20 -0
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/branching.test.d.ts +2 -0
- package/dist/tests/branching.test.d.ts.map +1 -0
- package/dist/tests/branching.test.js +99 -0
- package/dist/tests/branching.test.js.map +1 -0
- package/dist/tests/coValueCore.test.js +2 -3
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/sync.sharding.test.js +63 -0
- package/dist/tests/sync.sharding.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +1 -3
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +1 -3
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +1 -0
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +1 -1
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/coValueCore/branching.ts +198 -0
- package/src/coValueCore/coValueCore.ts +255 -72
- package/src/coValueCore/decodeTransactionChangesAndMeta.ts +81 -0
- package/src/coValueCore/verifiedState.ts +1 -1
- package/src/coValues/account.ts +11 -9
- package/src/coValues/coList.ts +8 -10
- package/src/coValues/coMap.ts +8 -11
- package/src/coValues/coStream.ts +7 -8
- package/src/ids.ts +4 -1
- package/src/jsonStringify.ts +8 -0
- package/src/permissions.ts +80 -89
- package/src/sync.ts +21 -6
- package/src/tests/account.test.ts +24 -0
- package/src/tests/branching.test.ts +141 -0
- package/src/tests/coValueCore.test.ts +2 -3
- package/src/tests/sync.sharding.test.ts +72 -0
- package/src/tests/sync.storage.test.ts +3 -3
- package/src/tests/sync.upload.test.ts +3 -3
- package/src/tests/testUtils.ts +2 -1
package/src/coValues/coStream.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { isAccountID } from "../typeUtils/isAccountID.js";
|
|
|
13
13
|
import { isCoValue } from "../typeUtils/isCoValue.js";
|
|
14
14
|
import { RawAccountID } from "./account.js";
|
|
15
15
|
import { RawGroup } from "./group.js";
|
|
16
|
+
import { Transaction } from "../coValueCore/verifiedState.js";
|
|
16
17
|
|
|
17
18
|
export type BinaryStreamInfo = {
|
|
18
19
|
mimeType: string;
|
|
@@ -58,15 +59,18 @@ export class RawCoStreamView<
|
|
|
58
59
|
[key: SessionID]: CoStreamItem<Item>[];
|
|
59
60
|
};
|
|
60
61
|
/** @internal */
|
|
61
|
-
knownTransactions:
|
|
62
|
-
totalValidTransactions = 0;
|
|
62
|
+
knownTransactions: Set<Transaction>;
|
|
63
63
|
readonly _item!: Item;
|
|
64
64
|
|
|
65
|
+
get totalValidTransactions() {
|
|
66
|
+
return this.knownTransactions.size;
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
constructor(core: AvailableCoValueCore) {
|
|
66
70
|
this.id = core.id as CoID<this>;
|
|
67
71
|
this.core = core;
|
|
68
72
|
this.items = {};
|
|
69
|
-
this.knownTransactions =
|
|
73
|
+
this.knownTransactions = new Set<Transaction>();
|
|
70
74
|
this.processNewTransactions();
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -113,7 +117,6 @@ export class RawCoStreamView<
|
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
for (const { txID, madeAt, changes } of newValidTransactions) {
|
|
116
|
-
this.totalValidTransactions++;
|
|
117
120
|
for (const changeUntyped of changes) {
|
|
118
121
|
const change = changeUntyped as Item;
|
|
119
122
|
let entries = this.items[txID.sessionID];
|
|
@@ -124,10 +127,6 @@ export class RawCoStreamView<
|
|
|
124
127
|
entries.push({ value: change, madeAt, tx: txID });
|
|
125
128
|
changeEntries.add(entries);
|
|
126
129
|
}
|
|
127
|
-
this.knownTransactions[txID.sessionID] = Math.max(
|
|
128
|
-
this.knownTransactions[txID.sessionID] ?? 0,
|
|
129
|
-
txID.txIndex,
|
|
130
|
-
);
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
for (const entries of changeEntries) {
|
package/src/ids.ts
CHANGED
|
@@ -20,7 +20,10 @@ export function rawCoIDfromBytes(bytes: Uint8Array): RawCoID {
|
|
|
20
20
|
return `co_z${base58.encode(bytes.slice(0, shortHashLength))}` as RawCoID;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export type TransactionID = {
|
|
23
|
+
export type TransactionID = {
|
|
24
|
+
sessionID: SessionID;
|
|
25
|
+
txIndex: number;
|
|
26
|
+
};
|
|
24
27
|
|
|
25
28
|
export type AgentID = `sealer_z${string}/signer_z${string}`;
|
|
26
29
|
|
package/src/jsonStringify.ts
CHANGED
|
@@ -66,3 +66,11 @@ export function stableStringify<T>(
|
|
|
66
66
|
export function parseJSON<T>(json: Stringified<T>): T {
|
|
67
67
|
return JSON.parse(json);
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
export function safeParseJSON<T>(json: Stringified<T>): T | undefined {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(json);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/permissions.ts
CHANGED
|
@@ -77,10 +77,7 @@ function logPermissionError(
|
|
|
77
77
|
logger.debug("Permission error: " + message, attributes);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export function determineValidTransactions(
|
|
81
|
-
coValue: CoValueCore,
|
|
82
|
-
knownTransactions?: CoValueKnownState["sessions"],
|
|
83
|
-
): { txID: TransactionID; tx: Transaction }[] {
|
|
80
|
+
export function determineValidTransactions(coValue: CoValueCore) {
|
|
84
81
|
if (!coValue.isAvailable()) {
|
|
85
82
|
throw new Error("determineValidTransactions CoValue is not available");
|
|
86
83
|
}
|
|
@@ -91,8 +88,7 @@ export function determineValidTransactions(
|
|
|
91
88
|
throw new Error("Group must have initialAdmin");
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
.validTransactions;
|
|
91
|
+
determineValidTransactionsForGroup(coValue, initialAdmin);
|
|
96
92
|
} else if (coValue.verified.header.ruleset.type === "ownedByGroup") {
|
|
97
93
|
const groupContent = expectGroup(
|
|
98
94
|
coValue.node
|
|
@@ -107,58 +103,44 @@ export function determineValidTransactions(
|
|
|
107
103
|
throw new Error("Group must be a map");
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const knownTransactionsForSession = knownTransactions?.[sessionID] ?? -1;
|
|
106
|
+
for (const tx of coValue.verifiedTransactions) {
|
|
107
|
+
if (tx.isValidated) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
115
110
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
111
|
+
tx.isValidated = true;
|
|
112
|
+
const wasValid = tx.isValid;
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
const groupAtTime = groupContent.atTime(tx.madeAt);
|
|
115
|
+
const effectiveTransactor = agentInAccountOrMemberInGroup(
|
|
116
|
+
tx.author,
|
|
117
|
+
groupAtTime,
|
|
118
|
+
);
|
|
126
119
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
if (!effectiveTransactor) {
|
|
121
|
+
tx.isValid = false;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
130
124
|
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
const transactorRoleAtTxTime =
|
|
126
|
+
groupAtTime.roleOfInternal(effectiveTransactor);
|
|
133
127
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
128
|
+
if (
|
|
129
|
+
transactorRoleAtTxTime !== "admin" &&
|
|
130
|
+
transactorRoleAtTxTime !== "writer" &&
|
|
131
|
+
transactorRoleAtTxTime !== "writeOnly"
|
|
132
|
+
) {
|
|
133
|
+
tx.isValid = false;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
});
|
|
137
|
+
tx.isValid = true;
|
|
144
138
|
}
|
|
145
|
-
|
|
146
|
-
return validTransactions;
|
|
147
139
|
} else if (coValue.verified.header.ruleset.type === "unsafeAllowAll") {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const knownTransactionsForSession = knownTransactions?.[sessionID] ?? -1;
|
|
152
|
-
|
|
153
|
-
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
154
|
-
if (knownTransactionsForSession >= txIndex) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
validTransactions.push({ txID: { sessionID, txIndex }, tx });
|
|
159
|
-
});
|
|
140
|
+
for (const tx of coValue.verifiedTransactions) {
|
|
141
|
+
tx.isValid = true;
|
|
142
|
+
tx.isValidated = true;
|
|
160
143
|
}
|
|
161
|
-
return validTransactions;
|
|
162
144
|
} else {
|
|
163
145
|
throw new Error(
|
|
164
146
|
"Unknown ruleset type " +
|
|
@@ -228,22 +210,9 @@ function determineValidTransactionsForGroup(
|
|
|
228
210
|
coValue: CoValueCore,
|
|
229
211
|
initialAdmin: RawAccountID | AgentID,
|
|
230
212
|
extendChain?: Set<CoValueCore["id"]>,
|
|
231
|
-
): {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
txIndex: number;
|
|
235
|
-
tx: Transaction;
|
|
236
|
-
}[] = [];
|
|
237
|
-
|
|
238
|
-
for (const [sessionID, sessionLog] of coValue.verified?.sessions.entries() ??
|
|
239
|
-
[]) {
|
|
240
|
-
sessionLog.transactions.forEach((tx, txIndex) => {
|
|
241
|
-
allTransactionsSorted.push({ sessionID, txIndex, tx });
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
allTransactionsSorted.sort((a, b) => {
|
|
246
|
-
return a.tx.madeAt - b.tx.madeAt;
|
|
213
|
+
): { memberState: MemberState } {
|
|
214
|
+
coValue.verifiedTransactions.sort((a, b) => {
|
|
215
|
+
return a.madeAt - b.madeAt;
|
|
247
216
|
});
|
|
248
217
|
|
|
249
218
|
const memberState: MemberState = {};
|
|
@@ -252,15 +221,16 @@ function determineValidTransactionsForGroup(
|
|
|
252
221
|
|
|
253
222
|
const writeKeys = new Set<string>();
|
|
254
223
|
|
|
255
|
-
for (const
|
|
256
|
-
const transactor =
|
|
224
|
+
for (const transaction of coValue.verifiedTransactions) {
|
|
225
|
+
const transactor = transaction.author;
|
|
226
|
+
|
|
227
|
+
transaction.isValidated = true;
|
|
228
|
+
|
|
229
|
+
const tx = transaction.tx;
|
|
257
230
|
|
|
258
231
|
if (tx.privacy === "private") {
|
|
259
232
|
if (memberState[transactor] === "admin") {
|
|
260
|
-
|
|
261
|
-
txID: { sessionID, txIndex },
|
|
262
|
-
tx,
|
|
263
|
-
});
|
|
233
|
+
transaction.isValid = true;
|
|
264
234
|
continue;
|
|
265
235
|
} else {
|
|
266
236
|
logPermissionError(
|
|
@@ -270,16 +240,20 @@ function determineValidTransactionsForGroup(
|
|
|
270
240
|
}
|
|
271
241
|
}
|
|
272
242
|
|
|
273
|
-
let changes;
|
|
243
|
+
let changes = transaction.changes;
|
|
274
244
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
245
|
+
if (!changes) {
|
|
246
|
+
try {
|
|
247
|
+
changes = parseJSON(tx.changes);
|
|
248
|
+
transaction.changes = changes;
|
|
249
|
+
} catch (e) {
|
|
250
|
+
logPermissionError("Invalid JSON in transaction", {
|
|
251
|
+
id: coValue.id,
|
|
252
|
+
tx,
|
|
253
|
+
});
|
|
254
|
+
transaction.hasInvalidChanges = true;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
283
257
|
}
|
|
284
258
|
|
|
285
259
|
const change = changes[0] as
|
|
@@ -292,29 +266,33 @@ function determineValidTransactionsForGroup(
|
|
|
292
266
|
|
|
293
267
|
if (changes.length !== 1) {
|
|
294
268
|
logPermissionError("Group transaction must have exactly one change");
|
|
269
|
+
transaction.isValid = false;
|
|
295
270
|
continue;
|
|
296
271
|
}
|
|
297
272
|
|
|
298
273
|
if (change.op !== "set") {
|
|
299
274
|
logPermissionError("Group transaction must set a role or readKey");
|
|
275
|
+
transaction.isValid = false;
|
|
300
276
|
continue;
|
|
301
277
|
}
|
|
302
278
|
|
|
303
279
|
if (change.key === "readKey") {
|
|
304
280
|
if (memberState[transactor] !== "admin") {
|
|
305
281
|
logPermissionError("Only admins can set readKeys");
|
|
282
|
+
transaction.isValid = false;
|
|
306
283
|
continue;
|
|
307
284
|
}
|
|
308
285
|
|
|
309
|
-
|
|
286
|
+
transaction.isValid = true;
|
|
310
287
|
continue;
|
|
311
288
|
} else if (change.key === "profile") {
|
|
312
289
|
if (memberState[transactor] !== "admin") {
|
|
313
290
|
logPermissionError("Only admins can set profile");
|
|
291
|
+
transaction.isValid = false;
|
|
314
292
|
continue;
|
|
315
293
|
}
|
|
316
294
|
|
|
317
|
-
|
|
295
|
+
transaction.isValid = true;
|
|
318
296
|
continue;
|
|
319
297
|
} else if (change.key === "root") {
|
|
320
298
|
if (memberState[transactor] !== "admin") {
|
|
@@ -322,7 +300,7 @@ function determineValidTransactionsForGroup(
|
|
|
322
300
|
continue;
|
|
323
301
|
}
|
|
324
302
|
|
|
325
|
-
|
|
303
|
+
transaction.isValid = true;
|
|
326
304
|
continue;
|
|
327
305
|
} else if (
|
|
328
306
|
isKeyForKeyField(change.key) ||
|
|
@@ -337,15 +315,17 @@ function determineValidTransactionsForGroup(
|
|
|
337
315
|
!isOwnWriteKeyRevelation(change.key, transactor, writeOnlyKeys)
|
|
338
316
|
) {
|
|
339
317
|
logPermissionError("Only admins can reveal keys");
|
|
318
|
+
transaction.isValid = false;
|
|
340
319
|
continue;
|
|
341
320
|
}
|
|
342
321
|
|
|
343
322
|
// TODO: check validity of agents who the key is revealed to?
|
|
344
|
-
|
|
323
|
+
transaction.isValid = true;
|
|
345
324
|
continue;
|
|
346
325
|
} else if (isParentExtension(change.key)) {
|
|
347
326
|
if (memberState[transactor] !== "admin") {
|
|
348
327
|
logPermissionError("Only admins can set parent extensions");
|
|
328
|
+
transaction.isValid = false;
|
|
349
329
|
continue;
|
|
350
330
|
}
|
|
351
331
|
|
|
@@ -364,13 +344,14 @@ function determineValidTransactionsForGroup(
|
|
|
364
344
|
logPermissionError(
|
|
365
345
|
"Circular extend detected, dropping the transaction",
|
|
366
346
|
);
|
|
347
|
+
transaction.isValid = false;
|
|
367
348
|
continue;
|
|
368
349
|
}
|
|
369
350
|
|
|
370
|
-
|
|
351
|
+
transaction.isValid = true;
|
|
371
352
|
continue;
|
|
372
353
|
} else if (isChildExtension(change.key)) {
|
|
373
|
-
|
|
354
|
+
transaction.isValid = true;
|
|
374
355
|
continue;
|
|
375
356
|
} else if (isWriteKeyForMember(change.key)) {
|
|
376
357
|
const memberKey = getAccountOrAgentFromWriteKeyForMember(change.key);
|
|
@@ -381,6 +362,7 @@ function determineValidTransactionsForGroup(
|
|
|
381
362
|
memberKey !== transactor
|
|
382
363
|
) {
|
|
383
364
|
logPermissionError("Only admins can set writeKeys");
|
|
365
|
+
transaction.isValid = false;
|
|
384
366
|
continue;
|
|
385
367
|
}
|
|
386
368
|
|
|
@@ -398,12 +380,13 @@ function determineValidTransactionsForGroup(
|
|
|
398
380
|
logPermissionError(
|
|
399
381
|
"Write key already exists and can't be overridden by invite",
|
|
400
382
|
);
|
|
383
|
+
transaction.isValid = false;
|
|
401
384
|
continue;
|
|
402
385
|
}
|
|
403
386
|
|
|
404
387
|
writeKeys.add(change.key);
|
|
405
388
|
|
|
406
|
-
|
|
389
|
+
transaction.isValid = true;
|
|
407
390
|
continue;
|
|
408
391
|
}
|
|
409
392
|
|
|
@@ -422,6 +405,7 @@ function determineValidTransactionsForGroup(
|
|
|
422
405
|
change.value !== "writeOnlyInvite"
|
|
423
406
|
) {
|
|
424
407
|
logPermissionError("Group transaction must set a valid role");
|
|
408
|
+
transaction.isValid = false;
|
|
425
409
|
continue;
|
|
426
410
|
}
|
|
427
411
|
|
|
@@ -437,6 +421,7 @@ function determineValidTransactionsForGroup(
|
|
|
437
421
|
logPermissionError(
|
|
438
422
|
"Everyone can only be set to reader, writer, writeOnly or revoked",
|
|
439
423
|
);
|
|
424
|
+
transaction.isValid = false;
|
|
440
425
|
continue;
|
|
441
426
|
}
|
|
442
427
|
|
|
@@ -460,41 +445,47 @@ function determineValidTransactionsForGroup(
|
|
|
460
445
|
assignedRole !== "admin"
|
|
461
446
|
) {
|
|
462
447
|
logPermissionError("Admins can only demote themselves.");
|
|
448
|
+
transaction.isValid = false;
|
|
463
449
|
continue;
|
|
464
450
|
}
|
|
465
451
|
} else if (memberState[transactor] === "adminInvite") {
|
|
466
452
|
if (change.value !== "admin") {
|
|
467
453
|
logPermissionError("AdminInvites can only create admins.");
|
|
454
|
+
transaction.isValid = false;
|
|
468
455
|
continue;
|
|
469
456
|
}
|
|
470
457
|
} else if (memberState[transactor] === "writerInvite") {
|
|
471
458
|
if (change.value !== "writer") {
|
|
472
459
|
logPermissionError("WriterInvites can only create writers.");
|
|
460
|
+
transaction.isValid = false;
|
|
473
461
|
continue;
|
|
474
462
|
}
|
|
475
463
|
} else if (memberState[transactor] === "readerInvite") {
|
|
476
464
|
if (change.value !== "reader") {
|
|
477
465
|
logPermissionError("ReaderInvites can only create reader.");
|
|
466
|
+
transaction.isValid = false;
|
|
478
467
|
continue;
|
|
479
468
|
}
|
|
480
469
|
} else if (memberState[transactor] === "writeOnlyInvite") {
|
|
481
470
|
if (change.value !== "writeOnly") {
|
|
482
471
|
logPermissionError("WriteOnlyInvites can only create writeOnly.");
|
|
472
|
+
transaction.isValid = false;
|
|
483
473
|
continue;
|
|
484
474
|
}
|
|
485
475
|
} else {
|
|
486
476
|
logPermissionError(
|
|
487
477
|
"Group transaction must be made by current admin or invite",
|
|
488
478
|
);
|
|
479
|
+
transaction.isValid = false;
|
|
489
480
|
continue;
|
|
490
481
|
}
|
|
491
482
|
}
|
|
492
483
|
|
|
493
484
|
memberState[affectedMember] = change.value;
|
|
494
|
-
|
|
485
|
+
transaction.isValid = true;
|
|
495
486
|
}
|
|
496
487
|
|
|
497
|
-
return {
|
|
488
|
+
return { memberState };
|
|
498
489
|
}
|
|
499
490
|
|
|
500
491
|
function agentInAccountOrMemberInGroup(
|
package/src/sync.ts
CHANGED
|
@@ -279,17 +279,32 @@ export class SyncManager {
|
|
|
279
279
|
peer.trackToldKnownState(id);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
reconcileServerPeers() {
|
|
283
|
+
const serverPeers = Object.values(this.peers).filter(
|
|
284
|
+
(peer) => peer.role === "server",
|
|
285
|
+
);
|
|
286
|
+
for (const peer of serverPeers) {
|
|
287
|
+
this.startPeerReconciliation(peer);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
282
291
|
startPeerReconciliation(peer: PeerState) {
|
|
283
292
|
const coValuesOrderedByDependency: CoValueCore[] = [];
|
|
284
293
|
|
|
285
|
-
const
|
|
286
|
-
|
|
294
|
+
const seen = new Set<string>();
|
|
287
295
|
const buildOrderedCoValueList = (coValue: CoValueCore) => {
|
|
288
|
-
if (
|
|
296
|
+
if (seen.has(coValue.id)) {
|
|
289
297
|
return;
|
|
290
298
|
}
|
|
299
|
+
seen.add(coValue.id);
|
|
291
300
|
|
|
292
|
-
|
|
301
|
+
// Ignore the covalue if this peer isn't relevant to it
|
|
302
|
+
if (
|
|
303
|
+
this.getServerPeers(coValue.id).find((p) => p.id === peer.id) ===
|
|
304
|
+
undefined
|
|
305
|
+
) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
293
308
|
|
|
294
309
|
for (const id of coValue.getDependedOnCoValues()) {
|
|
295
310
|
const coValue = this.local.getCoValue(id);
|
|
@@ -355,7 +370,7 @@ export class SyncManager {
|
|
|
355
370
|
});
|
|
356
371
|
}
|
|
357
372
|
|
|
358
|
-
addPeer(peer: Peer) {
|
|
373
|
+
addPeer(peer: Peer, skipReconciliation: boolean = false) {
|
|
359
374
|
const prevPeer = this.peers[peer.id];
|
|
360
375
|
|
|
361
376
|
if (prevPeer && !prevPeer.closed) {
|
|
@@ -373,7 +388,7 @@ export class SyncManager {
|
|
|
373
388
|
},
|
|
374
389
|
);
|
|
375
390
|
|
|
376
|
-
if (peerState.role === "server") {
|
|
391
|
+
if (!skipReconciliation && peerState.role === "server") {
|
|
377
392
|
void this.startPeerReconciliation(peerState);
|
|
378
393
|
}
|
|
379
394
|
|
|
@@ -137,6 +137,30 @@ test("Should migrate the root from private to trusting", async () => {
|
|
|
137
137
|
expect(account3.ops).toEqual(account2.ops); // No new transactions were made
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
test("myRole returns 'admin' for the current account", async () => {
|
|
141
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
142
|
+
creationProps: { name: "Hermes Puggington" },
|
|
143
|
+
crypto: Crypto,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const account = await node.load(accountID);
|
|
147
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
148
|
+
|
|
149
|
+
expect(account.myRole()).toEqual("admin");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("roleOf returns 'admin' when the accountID is the same as the receiver account", async () => {
|
|
153
|
+
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
154
|
+
creationProps: { name: "Hermes Puggington" },
|
|
155
|
+
crypto: Crypto,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const account = await node.load(accountID);
|
|
159
|
+
if (account === "unavailable") throw new Error("Account unavailable");
|
|
160
|
+
|
|
161
|
+
expect(account.roleOf(accountID)).toEqual("admin");
|
|
162
|
+
});
|
|
163
|
+
|
|
140
164
|
test("throws an error if the user tried to add a member to an account", async () => {
|
|
141
165
|
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
|
|
142
166
|
creationProps: { name: "Hermes Puggington" },
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { createTestNode } from "./testUtils.js";
|
|
3
|
+
import { expectMap } from "../coValue.js";
|
|
4
|
+
|
|
5
|
+
describe("Branching Logic", () => {
|
|
6
|
+
describe("Branch Operations with Transactions", () => {
|
|
7
|
+
test("should maintain separate transaction histories", async () => {
|
|
8
|
+
const node = createTestNode();
|
|
9
|
+
const group = node.createGroup();
|
|
10
|
+
const originalMap = group.createMap();
|
|
11
|
+
const branchName = "feature-branch";
|
|
12
|
+
|
|
13
|
+
// Add transactions to original map
|
|
14
|
+
originalMap.set("originalKey1", "value1", "trusting");
|
|
15
|
+
originalMap.set("originalKey2", "value2", "trusting");
|
|
16
|
+
|
|
17
|
+
const branch = expectMap(
|
|
18
|
+
originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// Add transactions to branch
|
|
22
|
+
branch.set("branchKey1", "branchValue1", "trusting");
|
|
23
|
+
branch.set("branchKey2", "branchValue2", "trusting");
|
|
24
|
+
|
|
25
|
+
expect(originalMap.get("branchKey1")).toBe(undefined);
|
|
26
|
+
expect(originalMap.get("branchKey2")).toBe(undefined);
|
|
27
|
+
expect(branch.get("branchKey2")).toBe("branchValue2");
|
|
28
|
+
expect(branch.get("branchKey1")).toBe("branchValue1");
|
|
29
|
+
expect(branch.get("originalKey1")).toBe("value1");
|
|
30
|
+
expect(branch.get("originalKey2")).toBe("value2");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("Branch Merging", () => {
|
|
35
|
+
test("should merge branch transactions back to source", () => {
|
|
36
|
+
const node = createTestNode();
|
|
37
|
+
const group = node.createGroup();
|
|
38
|
+
const originalMap = group.createMap();
|
|
39
|
+
const branchName = "feature-branch";
|
|
40
|
+
|
|
41
|
+
// Add transactions to original map
|
|
42
|
+
originalMap.set("key1", "value1", "trusting");
|
|
43
|
+
originalMap.set("key2", "value2", "trusting");
|
|
44
|
+
|
|
45
|
+
// Create branch
|
|
46
|
+
const branch = expectMap(
|
|
47
|
+
originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Add transactions to branch
|
|
51
|
+
branch.set("key1", "branchValue1", "trusting");
|
|
52
|
+
|
|
53
|
+
// Merge branch
|
|
54
|
+
const result = expectMap(branch.core.mergeBranch().getCurrentContent());
|
|
55
|
+
|
|
56
|
+
// Check that source now has branch transactions
|
|
57
|
+
expect(result.get("key1")).toBe("branchValue1");
|
|
58
|
+
expect(result.get("key2")).toBe("value2");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("should not merge branch transactions back to source if they are already merged", () => {
|
|
62
|
+
const node = createTestNode();
|
|
63
|
+
const group = node.createGroup();
|
|
64
|
+
const originalMap = group.createMap();
|
|
65
|
+
const branchName = "feature-branch";
|
|
66
|
+
|
|
67
|
+
// Add transactions to original map
|
|
68
|
+
originalMap.set("key1", "value1", "trusting");
|
|
69
|
+
originalMap.set("key2", "value2", "trusting");
|
|
70
|
+
|
|
71
|
+
// Create branch
|
|
72
|
+
const branch = expectMap(
|
|
73
|
+
originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Add transactions to branch
|
|
77
|
+
branch.set("key1", "branchValue1", "trusting");
|
|
78
|
+
|
|
79
|
+
// Merge branch twice
|
|
80
|
+
expectMap(branch.core.mergeBranch().getCurrentContent());
|
|
81
|
+
const result = expectMap(branch.core.mergeBranch().getCurrentContent());
|
|
82
|
+
|
|
83
|
+
expect(result.core.mergeCommits.length).toBe(1);
|
|
84
|
+
|
|
85
|
+
// Check that source now has branch transactions
|
|
86
|
+
expect(result.get("key1")).toBe("branchValue1");
|
|
87
|
+
expect(result.get("key2")).toBe("value2");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("should not create a merge commit if the branch is empty", () => {
|
|
91
|
+
const node = createTestNode();
|
|
92
|
+
const group = node.createGroup();
|
|
93
|
+
const originalMap = group.createMap();
|
|
94
|
+
const branchName = "feature-branch";
|
|
95
|
+
|
|
96
|
+
// Add transactions to original map
|
|
97
|
+
originalMap.set("key1", "value1", "trusting");
|
|
98
|
+
originalMap.set("key2", "value2", "trusting");
|
|
99
|
+
|
|
100
|
+
// Create branch
|
|
101
|
+
const branch = expectMap(
|
|
102
|
+
originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Merge branch twice
|
|
106
|
+
const result = expectMap(branch.core.mergeBranch().getCurrentContent());
|
|
107
|
+
|
|
108
|
+
expect(result.core.mergeCommits.length).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should merge the new changes from the branch after the last merge", () => {
|
|
112
|
+
const node = createTestNode();
|
|
113
|
+
const group = node.createGroup();
|
|
114
|
+
const originalMap = group.createMap();
|
|
115
|
+
const branchName = "feature-branch";
|
|
116
|
+
|
|
117
|
+
originalMap.set("key1", "value1", "trusting");
|
|
118
|
+
originalMap.set("key2", "value2", "trusting");
|
|
119
|
+
|
|
120
|
+
const branch = expectMap(
|
|
121
|
+
originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
branch.set("key1", "branchValue1", "trusting");
|
|
125
|
+
|
|
126
|
+
branch.core.mergeBranch();
|
|
127
|
+
|
|
128
|
+
expect(originalMap.get("key1")).toBe("branchValue1");
|
|
129
|
+
expect(originalMap.get("key2")).toBe("value2");
|
|
130
|
+
|
|
131
|
+
branch.set("key2", "branchValue2", "trusting");
|
|
132
|
+
|
|
133
|
+
branch.core.mergeBranch();
|
|
134
|
+
|
|
135
|
+
expect(originalMap.core.mergeCommits.length).toBe(2);
|
|
136
|
+
|
|
137
|
+
expect(originalMap.get("key1")).toBe("branchValue1");
|
|
138
|
+
expect(originalMap.get("key2")).toBe("branchValue2");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -277,9 +277,8 @@ test("creates a transaction with trusting meta information", async () => {
|
|
|
277
277
|
meta: true,
|
|
278
278
|
});
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
expect(validTransactions[0]?.tx.meta).toBe(`{"meta":true}`);
|
|
280
|
+
expect(map.core.verifiedTransactions[0]?.tx.meta).toBe(`{"meta":true}`);
|
|
281
|
+
expect(map.core.verifiedTransactions[0]?.meta).toEqual({ meta: true });
|
|
283
282
|
});
|
|
284
283
|
|
|
285
284
|
test("creates a transaction with private meta information", async () => {
|