cojson 0.18.0 → 0.18.2

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.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/coValueCore/branching.d.ts +36 -0
  4. package/dist/coValueCore/branching.d.ts.map +1 -0
  5. package/dist/coValueCore/branching.js +122 -0
  6. package/dist/coValueCore/branching.js.map +1 -0
  7. package/dist/coValueCore/coValueCore.d.ts +71 -5
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +162 -53
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +3 -0
  12. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +1 -0
  13. package/dist/coValueCore/decodeTransactionChangesAndMeta.js +59 -0
  14. package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +1 -0
  15. package/dist/coValueCore/utils.d.ts.map +1 -1
  16. package/dist/coValueCore/utils.js +3 -0
  17. package/dist/coValueCore/utils.js.map +1 -1
  18. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  19. package/dist/coValues/coList.d.ts +3 -3
  20. package/dist/coValues/coList.d.ts.map +1 -1
  21. package/dist/coValues/coList.js +4 -7
  22. package/dist/coValues/coList.js.map +1 -1
  23. package/dist/coValues/coMap.d.ts +3 -3
  24. package/dist/coValues/coMap.d.ts.map +1 -1
  25. package/dist/coValues/coMap.js +6 -6
  26. package/dist/coValues/coMap.js.map +1 -1
  27. package/dist/coValues/coStream.d.ts +3 -3
  28. package/dist/coValues/coStream.d.ts.map +1 -1
  29. package/dist/coValues/coStream.js +4 -4
  30. package/dist/coValues/coStream.js.map +1 -1
  31. package/dist/ids.d.ts.map +1 -1
  32. package/dist/ids.js.map +1 -1
  33. package/dist/jsonStringify.d.ts +1 -0
  34. package/dist/jsonStringify.d.ts.map +1 -1
  35. package/dist/jsonStringify.js +8 -0
  36. package/dist/jsonStringify.js.map +1 -1
  37. package/dist/localNode.d.ts +6 -0
  38. package/dist/localNode.d.ts.map +1 -1
  39. package/dist/localNode.js +27 -0
  40. package/dist/localNode.js.map +1 -1
  41. package/dist/permissions.d.ts +2 -7
  42. package/dist/permissions.d.ts.map +1 -1
  43. package/dist/permissions.js +75 -71
  44. package/dist/permissions.js.map +1 -1
  45. package/dist/tests/branching.test.d.ts +2 -0
  46. package/dist/tests/branching.test.d.ts.map +1 -0
  47. package/dist/tests/branching.test.js +287 -0
  48. package/dist/tests/branching.test.js.map +1 -0
  49. package/dist/tests/coValueCore.test.js +2 -3
  50. package/dist/tests/coValueCore.test.js.map +1 -1
  51. package/dist/tests/group.removeMember.test.js +63 -116
  52. package/dist/tests/group.removeMember.test.js.map +1 -1
  53. package/dist/tests/sync.load.test.js +36 -0
  54. package/dist/tests/sync.load.test.js.map +1 -1
  55. package/dist/tests/sync.storage.test.js +39 -3
  56. package/dist/tests/sync.storage.test.js.map +1 -1
  57. package/dist/tests/sync.upload.test.js +39 -3
  58. package/dist/tests/sync.upload.test.js.map +1 -1
  59. package/package.json +2 -2
  60. package/src/coValueCore/branching.ts +198 -0
  61. package/src/coValueCore/coValueCore.ts +255 -72
  62. package/src/coValueCore/decodeTransactionChangesAndMeta.ts +81 -0
  63. package/src/coValueCore/utils.ts +4 -0
  64. package/src/coValueCore/verifiedState.ts +1 -1
  65. package/src/coValues/coList.ts +8 -10
  66. package/src/coValues/coMap.ts +8 -11
  67. package/src/coValues/coStream.ts +7 -8
  68. package/src/ids.ts +4 -1
  69. package/src/jsonStringify.ts +8 -0
  70. package/src/localNode.ts +40 -0
  71. package/src/permissions.ts +83 -90
  72. package/src/tests/branching.test.ts +425 -0
  73. package/src/tests/coValueCore.test.ts +2 -3
  74. package/src/tests/group.removeMember.test.ts +116 -214
  75. package/src/tests/sync.load.test.ts +48 -0
  76. package/src/tests/sync.storage.test.ts +54 -3
  77. package/src/tests/sync.upload.test.ts +53 -3
@@ -0,0 +1,81 @@
1
+ import { AvailableCoValueCore, VerifiedTransaction } from "./coValueCore.js";
2
+ import { safeParseJSON } from "../jsonStringify.js";
3
+
4
+ export function decodeTransactionChangesAndMeta(
5
+ coValue: AvailableCoValueCore,
6
+ transaction: VerifiedTransaction,
7
+ ignorePrivateTransactions: boolean,
8
+ ) {
9
+ if (!transaction.isValid) {
10
+ return;
11
+ }
12
+
13
+ if (transaction.tx.privacy === "private" && ignorePrivateTransactions) {
14
+ return;
15
+ }
16
+
17
+ const needsChagesParsing =
18
+ !transaction.hasInvalidChanges && !transaction.changes;
19
+ const needsMetaParsing =
20
+ !transaction.hasInvalidMeta && !transaction.meta && transaction.tx.meta;
21
+
22
+ if (!needsChagesParsing && !needsMetaParsing) {
23
+ return;
24
+ }
25
+
26
+ if (transaction.tx.privacy === "private") {
27
+ const readKey = coValue.getReadKey(transaction.tx.keyUsed);
28
+
29
+ if (!readKey) {
30
+ return;
31
+ }
32
+
33
+ if (needsChagesParsing) {
34
+ const changes = coValue.verified.decryptTransaction(
35
+ transaction.txID.sessionID,
36
+ transaction.txID.txIndex,
37
+ readKey,
38
+ );
39
+
40
+ if (!changes) {
41
+ transaction.hasInvalidChanges = true;
42
+ } else {
43
+ transaction.changes = changes;
44
+ }
45
+ }
46
+
47
+ if (needsMetaParsing) {
48
+ const meta = coValue.verified.decryptTransactionMeta(
49
+ transaction.txID.sessionID,
50
+ transaction.txID.txIndex,
51
+ readKey,
52
+ );
53
+
54
+ if (!meta) {
55
+ transaction.hasInvalidMeta = true;
56
+ } else {
57
+ transaction.meta = meta;
58
+ }
59
+ }
60
+ } else {
61
+ if (needsChagesParsing) {
62
+ const changes = safeParseJSON(transaction.tx.changes);
63
+
64
+ if (!changes) {
65
+ transaction.hasInvalidChanges = true;
66
+ } else {
67
+ transaction.changes = changes;
68
+ }
69
+ }
70
+
71
+ if (needsMetaParsing && transaction.tx.meta) {
72
+ const meta = safeParseJSON(transaction.tx.meta);
73
+
74
+ if (!meta) {
75
+ transaction.hasInvalidMeta = true;
76
+ } else {
77
+ transaction.meta = meta;
78
+ }
79
+ }
80
+ }
81
+ }
@@ -52,6 +52,10 @@ export function getDependedOnCoValuesFromRawData(
52
52
  deps.add(header.ruleset.group);
53
53
  }
54
54
 
55
+ if (header.meta?.source) {
56
+ deps.add(header.meta.source as RawCoID);
57
+ }
58
+
55
59
  return deps;
56
60
  }
57
61
 
@@ -21,6 +21,7 @@ import { CoValueKnownState, NewContentMessage } from "../sync.js";
21
21
  import { TryAddTransactionsError } from "./coValueCore.js";
22
22
  import { SessionLog, SessionMap } from "./SessionMap.js";
23
23
  import { ControlledAccountOrAgent } from "../coValues/account.js";
24
+ import { logger } from "../logger.js";
24
25
 
25
26
  export type CoValueHeader = {
26
27
  type: AnyRawCoValue["type"];
@@ -40,7 +41,6 @@ export type PrivateTransaction = {
40
41
  encryptedChanges: Encrypted<JsonValue[], { in: RawCoID; tx: TransactionID }>;
41
42
  meta?: Encrypted<JsonObject, { in: RawCoID; tx: TransactionID }>;
42
43
  };
43
-
44
44
  export type TrustingTransaction = {
45
45
  privacy: "trusting";
46
46
  madeAt: number;
@@ -10,6 +10,7 @@ import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfrom
10
10
  import { isCoValue } from "../typeUtils/isCoValue.js";
11
11
  import { RawAccountID } from "./account.js";
12
12
  import { RawGroup } from "./group.js";
13
+ import { Transaction } from "../coValueCore/verifiedState.js";
13
14
 
14
15
  export type OpID = TransactionID & { changeIdx: number };
15
16
 
@@ -86,8 +87,12 @@ export class RawCoList<
86
87
  opID: OpID;
87
88
  }[];
88
89
  /** @internal */
89
- totalValidTransactions = 0;
90
- knownTransactions: CoValueKnownState["sessions"] = {};
90
+ knownTransactions: Set<Transaction>;
91
+
92
+ get totalValidTransactions() {
93
+ return this.knownTransactions.size;
94
+ }
95
+
91
96
  lastValidTransaction: number | undefined;
92
97
 
93
98
  /** @internal */
@@ -99,7 +104,7 @@ export class RawCoList<
99
104
  this.deletionsByInsertion = {};
100
105
  this.afterStart = [];
101
106
  this.beforeEnd = [];
102
- this.knownTransactions = {};
107
+ this.knownTransactions = new Set<Transaction>();
103
108
 
104
109
  this.processNewTransactions();
105
110
  }
@@ -114,7 +119,6 @@ export class RawCoList<
114
119
  return;
115
120
  }
116
121
 
117
- this.totalValidTransactions += transactions.length;
118
122
  let lastValidTransaction: number | undefined = undefined;
119
123
  let oldestValidTransaction: number | undefined = undefined;
120
124
  this._cachedEntries = undefined;
@@ -126,11 +130,6 @@ export class RawCoList<
126
130
  madeAt,
127
131
  );
128
132
 
129
- this.knownTransactions[txID.sessionID] = Math.max(
130
- this.knownTransactions[txID.sessionID] ?? 0,
131
- txID.txIndex,
132
- );
133
-
134
133
  for (const [changeIdx, changeUntyped] of changes.entries()) {
135
134
  const change = changeUntyped as ListOpPayload<Item>;
136
135
 
@@ -624,7 +623,6 @@ export class RawCoList<
624
623
  this.afterStart = listAfter.afterStart;
625
624
  this.beforeEnd = listAfter.beforeEnd;
626
625
  this.insertions = listAfter.insertions;
627
- this.totalValidTransactions = listAfter.totalValidTransactions;
628
626
  this.lastValidTransaction = listAfter.lastValidTransaction;
629
627
  this.knownTransactions = listAfter.knownTransactions;
630
628
  this.deletionsByInsertion = listAfter.deletionsByInsertion;
@@ -10,6 +10,7 @@ import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfrom
10
10
  import { isCoValue } from "../typeUtils/isCoValue.js";
11
11
  import { RawAccountID } from "./account.js";
12
12
  import type { RawGroup } from "./group.js";
13
+ import { Transaction } from "../coValueCore/verifiedState.js";
13
14
 
14
15
  type MapOp<K extends string, V extends JsonValue | undefined> = {
15
16
  txID: TransactionID;
@@ -57,7 +58,7 @@ export class RawCoMapView<
57
58
  [Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
58
59
  };
59
60
  /** @internal */
60
- knownTransactions: CoValueKnownState["sessions"];
61
+ knownTransactions: Set<Transaction>;
61
62
 
62
63
  /** @internal */
63
64
  ignorePrivateTransactions: boolean;
@@ -66,7 +67,9 @@ export class RawCoMapView<
66
67
  /** @category 6. Meta */
67
68
  readonly _shape!: Shape;
68
69
 
69
- totalValidTransactions = 0;
70
+ get totalValidTransactions() {
71
+ return this.knownTransactions.size;
72
+ }
70
73
 
71
74
  /** @internal */
72
75
  constructor(
@@ -83,7 +86,7 @@ export class RawCoMapView<
83
86
  options?.ignorePrivateTransactions ?? false;
84
87
  this.ops = {};
85
88
  this.latest = {};
86
- this.knownTransactions = {};
89
+ this.knownTransactions = new Set<Transaction>();
87
90
 
88
91
  this.processNewTransactions();
89
92
  }
@@ -113,7 +116,7 @@ export class RawCoMapView<
113
116
  NonNullable<(typeof ops)[keyof typeof ops]>
114
117
  >();
115
118
 
116
- for (const { txID, changes, madeAt, trusting } of newValidTransactions) {
119
+ for (const { txID, changes, madeAt, tx } of newValidTransactions) {
117
120
  if (madeAt > this.latestTxMadeAt) {
118
121
  this.latestTxMadeAt = madeAt;
119
122
  }
@@ -128,7 +131,7 @@ export class RawCoMapView<
128
131
  madeAt,
129
132
  changeIdx,
130
133
  change,
131
- trusting,
134
+ trusting: tx.privacy === "trusting",
132
135
  };
133
136
 
134
137
  const entries = ops[change.key];
@@ -140,15 +143,9 @@ export class RawCoMapView<
140
143
  entries.push(entry);
141
144
  changedEntries.set(change.key, entries);
142
145
  }
143
- this.knownTransactions[txID.sessionID] = Math.max(
144
- this.knownTransactions[txID.sessionID] ?? 0,
145
- txID.txIndex,
146
- );
147
146
  }
148
147
  }
149
148
 
150
- this.totalValidTransactions += newValidTransactions.length;
151
-
152
149
  for (const entries of changedEntries.values()) {
153
150
  entries.sort(this.core.compareTransactions);
154
151
  }
@@ -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: CoValueKnownState["sessions"];
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 = { sessionID: SessionID; txIndex: number };
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
 
@@ -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/localNode.ts CHANGED
@@ -457,6 +457,46 @@ export class LocalNode {
457
457
  return core.getCurrentContent() as T;
458
458
  }
459
459
 
460
+ /**
461
+ * Loads a branch from a group coValue, creating a new one if it doesn't exist.
462
+ *
463
+ * Returns "unavailable" in case of errors or missing source.
464
+ */
465
+ async checkoutBranch<T extends RawCoValue>(
466
+ id: CoID<T>,
467
+ branch: string,
468
+ branchOwnerID?: RawCoID,
469
+ ): Promise<T | "unavailable"> {
470
+ const source = await this.loadCoValueCore(id);
471
+
472
+ if (!source.isAvailable()) {
473
+ return "unavailable";
474
+ }
475
+
476
+ const header = source.verified.header;
477
+
478
+ // Group and account coValues can't have branches
479
+ if (header.ruleset.type !== "ownedByGroup") {
480
+ logger.error("Can't checkout a branch from a group coValue", {
481
+ id,
482
+ });
483
+ return "unavailable";
484
+ }
485
+
486
+ const ownerID = branchOwnerID || header.ruleset.group;
487
+
488
+ const branchID = source.getBranchId(branch, ownerID);
489
+
490
+ // Passing skipRetry to true because otherwise creating a new branch would always take 1 retry delay
491
+ let branchCoValue = await this.loadCoValueCore(branchID, undefined, true);
492
+
493
+ if (!branchCoValue.isAvailable()) {
494
+ branchCoValue = source.createBranch(branch, ownerID);
495
+ }
496
+
497
+ return branchCoValue.getCurrentContent() as T;
498
+ }
499
+
460
500
  getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
461
501
  const coValue = this.getCoValue(id);
462
502