cojson 0.18.5 → 0.18.7

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 (103) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/coValueContentMessage.d.ts +2 -0
  4. package/dist/coValueContentMessage.d.ts.map +1 -1
  5. package/dist/coValueContentMessage.js +7 -0
  6. package/dist/coValueContentMessage.js.map +1 -1
  7. package/dist/coValueCore/SessionMap.d.ts +2 -2
  8. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  9. package/dist/coValueCore/SessionMap.js +2 -4
  10. package/dist/coValueCore/SessionMap.js.map +1 -1
  11. package/dist/coValueCore/branching.d.ts +35 -13
  12. package/dist/coValueCore/branching.d.ts.map +1 -1
  13. package/dist/coValueCore/branching.js +55 -37
  14. package/dist/coValueCore/branching.js.map +1 -1
  15. package/dist/coValueCore/coValueCore.d.ts +15 -7
  16. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  17. package/dist/coValueCore/coValueCore.js +126 -35
  18. package/dist/coValueCore/coValueCore.js.map +1 -1
  19. package/dist/coValueCore/verifiedState.d.ts +4 -2
  20. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  21. package/dist/coValueCore/verifiedState.js +6 -4
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coList.d.ts +8 -2
  24. package/dist/coValues/coList.d.ts.map +1 -1
  25. package/dist/coValues/coList.js +95 -58
  26. package/dist/coValues/coList.js.map +1 -1
  27. package/dist/coValues/coMap.d.ts +2 -2
  28. package/dist/coValues/coMap.d.ts.map +1 -1
  29. package/dist/coValues/coMap.js +8 -8
  30. package/dist/coValues/coMap.js.map +1 -1
  31. package/dist/coValues/group.d.ts.map +1 -1
  32. package/dist/coValues/group.js +14 -1
  33. package/dist/coValues/group.js.map +1 -1
  34. package/dist/config.d.ts +6 -0
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +8 -0
  37. package/dist/config.js.map +1 -1
  38. package/dist/crypto/PureJSCrypto.d.ts.map +1 -1
  39. package/dist/crypto/PureJSCrypto.js +14 -6
  40. package/dist/crypto/PureJSCrypto.js.map +1 -1
  41. package/dist/exports.d.ts +5 -4
  42. package/dist/exports.d.ts.map +1 -1
  43. package/dist/exports.js +3 -3
  44. package/dist/exports.js.map +1 -1
  45. package/dist/ids.d.ts +1 -0
  46. package/dist/ids.d.ts.map +1 -1
  47. package/dist/ids.js.map +1 -1
  48. package/dist/localNode.d.ts.map +1 -1
  49. package/dist/localNode.js +7 -2
  50. package/dist/localNode.js.map +1 -1
  51. package/dist/storage/storageAsync.d.ts.map +1 -1
  52. package/dist/storage/storageAsync.js +7 -4
  53. package/dist/storage/storageAsync.js.map +1 -1
  54. package/dist/storage/storageSync.d.ts.map +1 -1
  55. package/dist/storage/storageSync.js +7 -4
  56. package/dist/storage/storageSync.js.map +1 -1
  57. package/dist/sync.d.ts +1 -3
  58. package/dist/sync.d.ts.map +1 -1
  59. package/dist/sync.js +8 -18
  60. package/dist/sync.js.map +1 -1
  61. package/dist/tests/branching.test.js +166 -4
  62. package/dist/tests/branching.test.js.map +1 -1
  63. package/dist/tests/coList.test.js +367 -1
  64. package/dist/tests/coList.test.js.map +1 -1
  65. package/dist/tests/coValueCore.test.js +45 -1
  66. package/dist/tests/coValueCore.test.js.map +1 -1
  67. package/dist/tests/messagesTestUtils.d.ts +1 -1
  68. package/dist/tests/sync.content.test.d.ts +2 -0
  69. package/dist/tests/sync.content.test.d.ts.map +1 -0
  70. package/dist/tests/sync.content.test.js +120 -0
  71. package/dist/tests/sync.content.test.js.map +1 -0
  72. package/dist/tests/sync.load.test.js +4 -4
  73. package/dist/tests/sync.load.test.js.map +1 -1
  74. package/dist/tests/sync.storage.test.js +1 -1
  75. package/dist/tests/sync.storageAsync.test.js +6 -10
  76. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  77. package/dist/tests/sync.upload.test.js +2 -2
  78. package/dist/tests/testUtils.d.ts +2 -2
  79. package/package.json +2 -2
  80. package/src/coValueContentMessage.ts +13 -0
  81. package/src/coValueCore/SessionMap.ts +2 -2
  82. package/src/coValueCore/branching.ts +105 -60
  83. package/src/coValueCore/coValueCore.ts +163 -41
  84. package/src/coValueCore/verifiedState.ts +8 -0
  85. package/src/coValues/coList.ts +129 -78
  86. package/src/coValues/coMap.ts +10 -12
  87. package/src/coValues/group.ts +14 -1
  88. package/src/config.ts +9 -0
  89. package/src/crypto/PureJSCrypto.ts +25 -13
  90. package/src/exports.ts +13 -2
  91. package/src/ids.ts +1 -0
  92. package/src/localNode.ts +8 -2
  93. package/src/storage/storageAsync.ts +8 -5
  94. package/src/storage/storageSync.ts +8 -4
  95. package/src/sync.ts +8 -32
  96. package/src/tests/branching.test.ts +255 -4
  97. package/src/tests/coList.test.ts +529 -1
  98. package/src/tests/coValueCore.test.ts +62 -2
  99. package/src/tests/sync.content.test.ts +153 -0
  100. package/src/tests/sync.load.test.ts +4 -4
  101. package/src/tests/sync.storage.test.ts +1 -1
  102. package/src/tests/sync.storageAsync.test.ts +9 -12
  103. package/src/tests/sync.upload.test.ts +2 -2
@@ -1,8 +1,41 @@
1
- import { CoValueCore } from "../exports.js";
2
- import { RawCoID, SessionID } from "../ids.js";
3
- import { AvailableCoValueCore, idforHeader } from "./coValueCore.js";
4
- import { CoValueHeader } from "./verifiedState.js";
5
- import { CoValueKnownState } from "../sync.js";
1
+ import type { CoValueCore } from "../exports.js";
2
+ import type { RawCoID, SessionID } from "../ids.js";
3
+ import { type AvailableCoValueCore, idforHeader } from "./coValueCore.js";
4
+ import type { CoValueHeader } from "./verifiedState.js";
5
+ import type { CoValueKnownState } from "../sync.js";
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
+ };
6
39
 
7
40
  export function getBranchHeader({
8
41
  type,
@@ -48,30 +81,37 @@ export function getBranchId(
48
81
  );
49
82
  }
50
83
 
51
- if (!ownerId) {
52
- const header = coValue.verified.header;
84
+ const currentOwnerId = ownerId ?? getBranchOwnerId(coValue);
53
85
 
54
- // Group and account coValues can't have branches, so we return the source id
55
- if (header.ruleset.type !== "ownedByGroup") {
56
- return coValue.id;
57
- }
58
-
59
- ownerId = header.ruleset.group;
86
+ if (!currentOwnerId) {
87
+ return coValue.id;
60
88
  }
61
89
 
62
90
  const header = getBranchHeader({
63
91
  type: coValue.verified.header.type,
64
92
  branchName: name,
65
- ownerId,
93
+ ownerId: currentOwnerId,
66
94
  sourceId: coValue.id,
67
95
  });
68
96
 
69
97
  return idforHeader(header, coValue.node.crypto);
70
98
  }
71
99
 
72
- export type BranchCommit = {
73
- branch: CoValueKnownState["sessions"];
74
- };
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
+ }
75
115
 
76
116
  /**
77
117
  * Given a coValue, a branch name and an owner id, creates a new branch CoValue
@@ -87,32 +127,34 @@ export function createBranch(
87
127
  );
88
128
  }
89
129
 
90
- if (!ownerId) {
91
- const header = coValue.verified.header;
92
-
93
- // Group and account coValues can't have branches, so we return the source coValue
94
- if (header.ruleset.type !== "ownedByGroup") {
95
- return coValue;
96
- }
130
+ const branchOwnerId = ownerId ?? getBranchOwnerId(coValue);
97
131
 
98
- ownerId = header.ruleset.group;
132
+ if (!branchOwnerId) {
133
+ return coValue;
99
134
  }
100
135
 
101
136
  const header = getBranchHeader({
102
137
  type: coValue.verified.header.type,
103
138
  branchName: name,
104
- ownerId,
139
+ ownerId: branchOwnerId,
105
140
  sourceId: coValue.id,
106
141
  });
107
142
 
108
- const value = coValue.node.createCoValue(header);
143
+ const branch = coValue.node.createCoValue(header);
144
+ const sessions = { ...coValue.knownState().sessions };
109
145
 
110
146
  // Create a branch commit to identify the starting point of the branch
111
- value.makeTransaction([], "private", {
112
- branch: coValue.knownState().sessions,
113
- } satisfies BranchCommit);
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);
114
156
 
115
- return value;
157
+ return branch;
116
158
  }
117
159
 
118
160
  /**
@@ -125,7 +167,7 @@ export function getBranchSource(
125
167
  return undefined;
126
168
  }
127
169
 
128
- const sourceId = coValue.verified.header.meta?.source;
170
+ const sourceId = coValue.getCurrentBranchSourceId();
129
171
 
130
172
  if (!sourceId) {
131
173
  return undefined;
@@ -140,15 +182,6 @@ export function getBranchSource(
140
182
  return source;
141
183
  }
142
184
 
143
- export type MergeCommit = {
144
- // The point where the branch was merged
145
- merge: CoValueKnownState["sessions"];
146
- // The id of the branch that was merged
147
- id: RawCoID;
148
- // The number of transactions that were merged, will be used in the future to handle the edits history properly
149
- count: number;
150
- };
151
-
152
185
  /**
153
186
  * Given a branch coValue, merges the branch into the source coValue
154
187
  */
@@ -159,10 +192,8 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
159
192
  );
160
193
  }
161
194
 
162
- const sourceId = branch.verified.header.meta?.source;
163
-
164
- if (!sourceId) {
165
- throw new Error("CoValueCore: mergeBranch called on a non-branch coValue");
195
+ if (branch.verified.header.ruleset.type !== "ownedByGroup") {
196
+ return branch;
166
197
  }
167
198
 
168
199
  const target = getBranchSource(branch);
@@ -173,13 +204,9 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
173
204
 
174
205
  // Look for previous merge commits, to see which transactions needs to be merged
175
206
  // Done mostly for performance reasons, as we could merge all the transactions every time and nothing would change
176
- const mergedTransactions = target.mergeCommits.reduce(
177
- (acc, { commit }) => {
178
- if (commit.id !== branch.id) {
179
- return acc;
180
- }
181
-
182
- 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 [
183
210
  SessionID,
184
211
  number,
185
212
  ][]) {
@@ -205,16 +232,34 @@ export function mergeBranch(branch: CoValueCore): CoValueCore {
205
232
  return target;
206
233
  }
207
234
 
208
- // Create a merge commit to identify the merge point
209
- target.makeTransaction([], "private", {
210
- merge: { ...branch.knownState().sessions },
211
- id: branch.id,
212
- count: branchValidTransactions.length,
213
- } satisfies MergeCommit);
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;
240
+
241
+ for (const tx of branchValidTransactions) {
242
+ const mergeMeta: MergedTransactionMetadata = {
243
+ mi: tx.txID.txIndex,
244
+ };
214
245
 
215
- for (const { tx, changes } of branchValidTransactions) {
216
- target.makeTransaction(changes, tx.privacy);
246
+ if (lastSessionId !== tx.txID.sessionID) {
247
+ mergeMeta.s = tx.txID.sessionID;
248
+ }
249
+
250
+ if (lastBranchId !== tx.txID.branch) {
251
+ mergeMeta.b = tx.txID.branch;
252
+ }
253
+
254
+ target.makeTransaction(tx.changes, tx.tx.privacy, mergeMeta, tx.madeAt);
255
+ lastSessionId = tx.txID.sessionID;
256
+ lastBranchId = tx.txID.branch;
217
257
  }
218
258
 
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);
263
+
219
264
  return target;
220
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 = {
@@ -215,6 +221,20 @@ export class CoValueCore {
215
221
  });
216
222
  }
217
223
 
224
+ waitForFullStreaming(): Promise<CoValueCore> {
225
+ return new Promise<CoValueCore>((resolve) => {
226
+ const listener = (core: CoValueCore) => {
227
+ if (core.isAvailable() && !core.verified.isStreaming()) {
228
+ resolve(core);
229
+ this.listeners.delete(listener);
230
+ }
231
+ };
232
+
233
+ this.listeners.add(listener);
234
+ listener(this);
235
+ });
236
+ }
237
+
218
238
  getStateForPeer(peerId: PeerID) {
219
239
  return this.peers.get(peerId);
220
240
  }
@@ -586,6 +606,7 @@ export class CoValueCore {
586
606
  changes: JsonValue[],
587
607
  privacy: "private" | "trusting",
588
608
  meta?: JsonObject,
609
+ madeAt?: number,
589
610
  ): boolean {
590
611
  if (!this.verified) {
591
612
  throw new Error(
@@ -593,6 +614,8 @@ export class CoValueCore {
593
614
  );
594
615
  }
595
616
 
617
+ validateTxSizeLimitInBytes(changes);
618
+
596
619
  // This is an ugly hack to get a unique but stable session ID for editing the current account
597
620
  const sessionID =
598
621
  this.verified.header.meta?.type === "account"
@@ -620,6 +643,7 @@ export class CoValueCore {
620
643
  keyID,
621
644
  keySecret,
622
645
  meta,
646
+ madeAt ?? Date.now(),
623
647
  );
624
648
  } else {
625
649
  result = this.verified.makeNewTrustingTransaction(
@@ -627,6 +651,7 @@ export class CoValueCore {
627
651
  signerAgent,
628
652
  changes,
629
653
  meta,
654
+ madeAt ?? Date.now(),
630
655
  );
631
656
  }
632
657
 
@@ -675,12 +700,13 @@ export class CoValueCore {
675
700
  }
676
701
 
677
702
  // The starting point of the branch, in case this CoValue is a branch
678
- branchStart:
679
- | { branch: CoValueKnownState["sessions"]; madeAt: number }
680
- | undefined;
703
+ branchStart: { from: BranchStartCommit["from"]; madeAt: number } | undefined;
681
704
 
682
705
  // The list of merge commits that have been made
683
- mergeCommits: { commit: MergeCommit; madeAt: number }[] = [];
706
+ mergeCommits: MergeCommit[] = [];
707
+ branches: BranchPointerCommit[] = [];
708
+ earliestTxMadeAt: number = Number.MAX_SAFE_INTEGER;
709
+ latestTxMadeAt: number = 0;
684
710
 
685
711
  // Reset the parsed transactions and branches, to validate them again from scratch when the group is updated
686
712
  resetParsedTransactions() {
@@ -696,6 +722,11 @@ export class CoValueCore {
696
722
  verifiedTransactions: VerifiedTransaction[] = [];
697
723
  private verifiedTransactionsKnownSessions: CoValueKnownState["sessions"] = {};
698
724
 
725
+ private lastVerifiedTransactionBySessionID: Record<
726
+ SessionID,
727
+ VerifiedTransaction
728
+ > = {};
729
+
699
730
  /**
700
731
  * Loads the new transaction from the SessionMap into verifiedTransactions as a VerifiedTransaction.
701
732
  *
@@ -717,6 +748,8 @@ export class CoValueCore {
717
748
  return;
718
749
  }
719
750
 
751
+ const isBranch = this.isBranch();
752
+
720
753
  for (const [sessionID, sessionLog] of this.verified.sessions.entries()) {
721
754
  const count = this.verifiedTransactionsKnownSessions[sessionID] ?? 0;
722
755
 
@@ -725,12 +758,20 @@ export class CoValueCore {
725
758
  return;
726
759
  }
727
760
 
728
- this.verifiedTransactions.push({
761
+ const txID = isBranch
762
+ ? {
763
+ sessionID,
764
+ txIndex,
765
+ branch: this.id,
766
+ }
767
+ : {
768
+ sessionID,
769
+ txIndex,
770
+ };
771
+
772
+ const verifiedTransaction = {
729
773
  author: accountOrAgentIDfromSessionID(sessionID),
730
- txID: {
731
- sessionID,
732
- txIndex,
733
- },
774
+ txID,
734
775
  madeAt: tx.madeAt,
735
776
  isValidated: false,
736
777
  isValid: false,
@@ -740,7 +781,20 @@ export class CoValueCore {
740
781
  hasInvalidMeta: false,
741
782
  hasMetaBeenParsed: false,
742
783
  tx,
743
- });
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;
744
798
  });
745
799
 
746
800
  this.verifiedTransactionsKnownSessions[sessionID] =
@@ -769,23 +823,54 @@ export class CoValueCore {
769
823
 
770
824
  transaction.hasMetaBeenParsed = true;
771
825
 
772
- if (
773
- transaction.meta?.["branch"] &&
774
- (!this.branchStart || transaction.madeAt < this.branchStart.madeAt)
775
- ) {
776
- this.branchStart = {
777
- branch: transaction.meta.branch as CoValueKnownState["sessions"],
778
- madeAt: transaction.madeAt,
779
- };
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
+ }
780
845
  }
781
846
 
782
- if (transaction.meta?.["merge"]) {
783
- const mergeCommit = transaction.meta as MergeCommit;
847
+ // Check if the transaction is a branch pointer
848
+ if ("branch" in transaction.meta) {
849
+ const branch = transaction.meta as BranchPointerCommit;
784
850
 
785
- this.mergeCommits.push({
786
- commit: mergeCommit,
787
- madeAt: transaction.madeAt,
788
- });
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
+ }
789
874
  }
790
875
  }
791
876
 
@@ -836,6 +921,8 @@ export class CoValueCore {
836
921
 
837
922
  const matchingTransactions: DecryptedTransaction[] = [];
838
923
 
924
+ const source = getBranchSource(this);
925
+
839
926
  for (const transaction of this.verifiedTransactions) {
840
927
  if (!isValidTransactionWithChanges(transaction)) {
841
928
  continue;
@@ -847,10 +934,12 @@ export class CoValueCore {
847
934
 
848
935
  options?.knownTransactions?.add(transaction.tx);
849
936
 
850
- const { txID, madeAt } = transaction;
937
+ const { txID } = transaction;
851
938
 
852
939
  const from = options?.from?.[txID.sessionID] ?? -1;
853
- const to = options?.to?.[txID.sessionID] ?? Infinity;
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;
854
943
 
855
944
  // The txIndex starts at 0 and from/to are referring to the count of transactions
856
945
  if (from > txID.txIndex || to < txID.txIndex) {
@@ -860,26 +949,16 @@ export class CoValueCore {
860
949
  matchingTransactions.push(transaction);
861
950
  }
862
951
 
863
- const source = getBranchSource(this);
864
-
865
952
  // If this is a branch, we load the valid transactions from the source
866
953
  if (source && this.branchStart && !options?.skipBranchSource) {
867
954
  const sourceTransactions = source.getValidTransactions({
868
- to: this.branchStart.branch,
955
+ to: this.branchStart.from,
869
956
  ignorePrivateTransactions: options?.ignorePrivateTransactions ?? false,
870
957
  knownTransactions: options?.knownTransactions,
871
958
  });
872
959
 
873
- for (const { changes, tx, madeAt, txID } of sourceTransactions) {
874
- matchingTransactions.push({
875
- txID: {
876
- sessionID: `${txID.sessionID}_branch_${source.id}`,
877
- txIndex: txID.txIndex,
878
- },
879
- madeAt,
880
- changes,
881
- tx,
882
- });
960
+ for (const transaction of sourceTransactions) {
961
+ matchingTransactions.push(transaction);
883
962
  }
884
963
  }
885
964
 
@@ -898,6 +977,49 @@ export class CoValueCore {
898
977
  return this.node.getCoValue(getBranchId(this, name, ownerId));
899
978
  }
900
979
 
980
+ getCurrentBranchName() {
981
+ return this.verified?.branchName;
982
+ }
983
+
984
+ getCurrentBranchSourceId() {
985
+ return this.verified?.branchSourceId;
986
+ }
987
+
988
+ isBranch() {
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;
1021
+ }
1022
+
901
1023
  getValidSortedTransactions(options?: {
902
1024
  ignorePrivateTransactions: boolean;
903
1025
 
@@ -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;