cojson 0.18.34 → 0.18.36

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 (48) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/coValueContentMessage.d.ts +5 -2
  4. package/dist/coValueContentMessage.d.ts.map +1 -1
  5. package/dist/coValueContentMessage.js +15 -0
  6. package/dist/coValueContentMessage.js.map +1 -1
  7. package/dist/coValueCore/SessionMap.d.ts +4 -3
  8. package/dist/coValueCore/SessionMap.d.ts.map +1 -1
  9. package/dist/coValueCore/SessionMap.js +12 -15
  10. package/dist/coValueCore/SessionMap.js.map +1 -1
  11. package/dist/coValueCore/coValueCore.d.ts +14 -6
  12. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  13. package/dist/coValueCore/coValueCore.js +29 -48
  14. package/dist/coValueCore/coValueCore.js.map +1 -1
  15. package/dist/coValueCore/verifiedState.d.ts +3 -3
  16. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  17. package/dist/coValueCore/verifiedState.js +18 -10
  18. package/dist/coValueCore/verifiedState.js.map +1 -1
  19. package/dist/coValues/group.d.ts.map +1 -1
  20. package/dist/coValues/group.js +21 -16
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/knownState.d.ts.map +1 -1
  23. package/dist/knownState.js.map +1 -1
  24. package/dist/localNode.d.ts +7 -2
  25. package/dist/localNode.d.ts.map +1 -1
  26. package/dist/localNode.js +8 -14
  27. package/dist/localNode.js.map +1 -1
  28. package/dist/sync.d.ts.map +1 -1
  29. package/dist/sync.js +15 -18
  30. package/dist/sync.js.map +1 -1
  31. package/dist/tests/coValueContentMessage.test.js +130 -1
  32. package/dist/tests/coValueContentMessage.test.js.map +1 -1
  33. package/dist/tests/coValueCore.test.js +3 -6
  34. package/dist/tests/coValueCore.test.js.map +1 -1
  35. package/dist/tests/group.childKeyRotation.test.js +6 -6
  36. package/dist/tests/group.childKeyRotation.test.js.map +1 -1
  37. package/package.json +3 -4
  38. package/src/coValueContentMessage.ts +29 -2
  39. package/src/coValueCore/SessionMap.ts +23 -16
  40. package/src/coValueCore/coValueCore.ts +37 -63
  41. package/src/coValueCore/verifiedState.ts +25 -15
  42. package/src/coValues/group.ts +41 -28
  43. package/src/knownState.ts +3 -12
  44. package/src/localNode.ts +10 -18
  45. package/src/sync.ts +24 -25
  46. package/src/tests/coValueContentMessage.test.ts +197 -2
  47. package/src/tests/coValueCore.test.ts +7 -10
  48. package/src/tests/group.childKeyRotation.test.ts +6 -6
@@ -3,9 +3,9 @@ import { TRANSACTION_CONFIG } from "./config.js";
3
3
  import { Signature } from "./crypto/crypto.js";
4
4
  import { RawCoID, SessionID } from "./ids.js";
5
5
  import { JsonValue } from "./jsonValue.js";
6
- import { emptyKnownState } from "./knownState.js";
6
+ import { CoValueKnownState, emptyKnownState } from "./knownState.js";
7
7
  import { getPriorityFromHeader } from "./priority.js";
8
- import { NewContentMessage } from "./sync.js";
8
+ import { NewContentMessage, SessionNewContent } from "./sync.js";
9
9
 
10
10
  export function createContentMessage(
11
11
  id: RawCoID,
@@ -104,3 +104,30 @@ export function getContenDebugInfo(msg: NewContentMessage) {
104
104
  `Session: ${sessionID} After: ${sessionNewContent.after} New: ${sessionNewContent.newTransactions.length}`,
105
105
  );
106
106
  }
107
+
108
+ export function getNewTransactionsFromContentMessage(
109
+ content: SessionNewContent,
110
+ knownState: CoValueKnownState,
111
+ sessionID: SessionID,
112
+ ) {
113
+ const ourKnownTxIdx = knownState.sessions[sessionID] ?? 0;
114
+ const theirFirstNewTxIdx = content.after;
115
+
116
+ if (ourKnownTxIdx < theirFirstNewTxIdx) {
117
+ // Flagging invalid state assumption by returning undefined
118
+ // not throwing an error or return an error object for performance reasons
119
+ return undefined;
120
+ }
121
+
122
+ const alreadyKnownOffset = ourKnownTxIdx - theirFirstNewTxIdx;
123
+
124
+ const newTransactions = content.newTransactions.slice(alreadyKnownOffset);
125
+
126
+ return newTransactions;
127
+ }
128
+
129
+ export function getSessionEntriesFromContentMessage(
130
+ content: NewContentMessage,
131
+ ) {
132
+ return Object.entries(content.new) as [SessionID, SessionNewContent][];
133
+ }
@@ -1,4 +1,3 @@
1
- import { Result, err, ok } from "neverthrow";
2
1
  import { ControlledAccountOrAgent } from "../coValues/account.js";
3
2
  import type {
4
3
  CryptoProvider,
@@ -40,6 +39,9 @@ export class SessionMap {
40
39
  // Known state related properies, mutated when adding transactions to the session map
41
40
  knownState: CoValueKnownState;
42
41
  knownStateWithStreaming: CoValueKnownState | undefined;
42
+ // The immutable version of the known statuses, to get a different reference when the known state is updated
43
+ immutableKnownState: CoValueKnownState;
44
+ immutableKnownStateWithStreaming: CoValueKnownState | undefined;
43
45
  streamingKnownState?: KnownStateSessions;
44
46
 
45
47
  constructor(
@@ -48,6 +50,7 @@ export class SessionMap {
48
50
  streamingKnownState?: KnownStateSessions,
49
51
  ) {
50
52
  this.knownState = { id: this.id, header: true, sessions: {} };
53
+ this.immutableKnownState = { id: this.id, header: true, sessions: {} };
51
54
  if (streamingKnownState) {
52
55
  this.streamingKnownState = { ...streamingKnownState };
53
56
  this.knownStateWithStreaming = {
@@ -55,6 +58,9 @@ export class SessionMap {
55
58
  header: true,
56
59
  sessions: { ...streamingKnownState },
57
60
  };
61
+ this.immutableKnownStateWithStreaming = cloneKnownState(
62
+ this.knownStateWithStreaming,
63
+ );
58
64
  }
59
65
  }
60
66
 
@@ -86,6 +92,17 @@ export class SessionMap {
86
92
  this.knownStateWithStreaming.sessions,
87
93
  actualStreamingKnownState,
88
94
  );
95
+
96
+ this.immutableKnownStateWithStreaming = cloneKnownState(
97
+ this.knownStateWithStreaming,
98
+ );
99
+ }
100
+
101
+ updateImmutableKnownState() {
102
+ this.immutableKnownState = cloneKnownState(this.knownState);
103
+ this.immutableKnownStateWithStreaming = this.knownStateWithStreaming
104
+ ? cloneKnownState(this.knownStateWithStreaming)
105
+ : undefined;
89
106
  }
90
107
 
91
108
  get(sessionID: SessionID): SessionLog | undefined {
@@ -120,24 +137,12 @@ export class SessionMap {
120
137
  newTransactions: Transaction[],
121
138
  newSignature: Signature,
122
139
  skipVerify: boolean = false,
123
- ): Result<true, TryAddTransactionsError> {
140
+ ) {
124
141
  const sessionLog = this.getOrCreateSessionLog(sessionID, signerID);
125
142
 
126
- try {
127
- sessionLog.impl.tryAdd(newTransactions, newSignature, skipVerify);
128
-
129
- this.addTransactionsToJsLog(sessionLog, newTransactions, newSignature);
143
+ sessionLog.impl.tryAdd(newTransactions, newSignature, skipVerify);
130
144
 
131
- return ok(true as const);
132
- } catch (e) {
133
- return err({
134
- type: "InvalidSignature",
135
- id: this.id,
136
- sessionID,
137
- newSignature,
138
- signerID,
139
- } satisfies TryAddTransactionsError);
140
- }
145
+ this.addTransactionsToJsLog(sessionLog, newTransactions, newSignature);
141
146
  }
142
147
 
143
148
  makeNewPrivateTransaction(
@@ -250,6 +255,8 @@ export class SessionMap {
250
255
  transactionsCount,
251
256
  );
252
257
  }
258
+
259
+ this.updateImmutableKnownState();
253
260
  }
254
261
 
255
262
  decryptTransaction(
@@ -1,5 +1,4 @@
1
1
  import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
2
- import { Result, err, ok } from "neverthrow";
3
2
  import type { PeerState } from "../PeerState.js";
4
3
  import type { RawCoValue } from "../coValue.js";
5
4
  import type { ControlledAccountOrAgent } from "../coValues/account.js";
@@ -201,7 +200,7 @@ export class CoValueCore {
201
200
  }
202
201
  | {
203
202
  type: "errored";
204
- error: TryAddTransactionsError;
203
+ error: unknown;
205
204
  }
206
205
  >();
207
206
 
@@ -474,12 +473,10 @@ export class CoValueCore {
474
473
  new SessionMap(this.id, this.node.crypto, streamingKnownState),
475
474
  );
476
475
 
477
- this.resetKnownStateCache();
478
-
479
476
  return true;
480
477
  }
481
478
 
482
- markErrored(peerId: PeerID, error: TryAddTransactionsError) {
479
+ markErrored(peerId: PeerID, error: unknown) {
483
480
  const previousState = this.loadingState;
484
481
  this.loadingStatuses.set(peerId, { type: "errored", error });
485
482
  this.updateCounter(previousState);
@@ -531,27 +528,18 @@ export class CoValueCore {
531
528
  .then((core) => core.getCurrentContent());
532
529
  }
533
530
 
534
- private _cachedKnownStateWithStreaming?: CoValueKnownState;
535
531
  /**
536
532
  * Returns the known state considering the known state of the streaming source
537
533
  *
538
534
  * Used to correctly manage the content & subscriptions during the content streaming process
539
535
  */
540
536
  knownStateWithStreaming(): CoValueKnownState {
541
- if (this._cachedKnownStateWithStreaming) {
542
- return this._cachedKnownStateWithStreaming;
543
- }
544
-
545
- const knownState = this.verified
546
- ? cloneKnownState(this.verified.knownStateWithStreaming())
547
- : emptyKnownState(this.id);
548
-
549
- this._cachedKnownStateWithStreaming = knownState;
550
-
551
- return knownState;
537
+ return (
538
+ this.verified?.immutableKnownStateWithStreaming() ??
539
+ emptyKnownState(this.id)
540
+ );
552
541
  }
553
542
 
554
- private _cachedKnownState?: CoValueKnownState;
555
543
  /**
556
544
  * Returns the known state of the CoValue
557
545
  *
@@ -560,17 +548,7 @@ export class CoValueCore {
560
548
  * On change the knownState is invalidated and a new object is returned.
561
549
  */
562
550
  knownState(): CoValueKnownState {
563
- if (this._cachedKnownState) {
564
- return this._cachedKnownState;
565
- }
566
-
567
- const knownState = this.verified
568
- ? cloneKnownState(this.verified.knownState())
569
- : emptyKnownState(this.id);
570
-
571
- this._cachedKnownState = knownState;
572
-
573
- return knownState;
551
+ return this.verified?.immutableKnownState() ?? emptyKnownState(this.id);
574
552
  }
575
553
 
576
554
  get meta(): JsonValue {
@@ -612,31 +590,36 @@ export class CoValueCore {
612
590
  newTransactions: Transaction[],
613
591
  newSignature: Signature,
614
592
  skipVerify: boolean = false,
615
- ): Result<true, TryAddTransactionsError> {
616
- let result: Result<SignerID | undefined, TryAddTransactionsError>;
593
+ ) {
594
+ let signerID: SignerID | undefined;
617
595
 
618
- if (skipVerify) {
619
- result = ok(undefined);
620
- } else {
621
- result = this.node
622
- .resolveAccountAgent(
623
- accountOrAgentIDfromSessionID(sessionID),
624
- "Expected to know signer of transaction",
625
- )
626
- .andThen((agent) => {
627
- return ok(this.crypto.getAgentSignerID(agent));
628
- });
629
- }
596
+ if (!skipVerify) {
597
+ const result = this.node.resolveAccountAgent(
598
+ accountOrAgentIDfromSessionID(sessionID),
599
+ "Expected to know signer of transaction",
600
+ );
630
601
 
631
- return result.andThen((signerID) => {
632
- if (!this.verified) {
633
- return err({
634
- type: "TriedToAddTransactionsWithoutVerifiedState",
602
+ if (result.error || !result.value) {
603
+ return {
604
+ type: "ResolveAccountAgentError",
635
605
  id: this.id,
636
- } satisfies TriedToAddTransactionsWithoutVerifiedStateErrpr);
606
+ error: result.error,
607
+ } as const;
637
608
  }
638
609
 
639
- const result = this.verified.tryAddTransactions(
610
+ signerID = this.crypto.getAgentSignerID(result.value);
611
+ }
612
+
613
+ if (!this.verified) {
614
+ return {
615
+ type: "TriedToAddTransactionsWithoutVerifiedState",
616
+ id: this.id,
617
+ error: undefined,
618
+ };
619
+ }
620
+
621
+ try {
622
+ this.verified.tryAddTransactions(
640
623
  sessionID,
641
624
  signerID,
642
625
  newTransactions,
@@ -644,19 +627,11 @@ export class CoValueCore {
644
627
  skipVerify,
645
628
  );
646
629
 
647
- if (result.isOk()) {
648
- this.resetKnownStateCache();
649
- this.processNewTransactions();
650
- this.scheduleNotifyUpdate();
651
- }
652
-
653
- return result;
654
- });
655
- }
656
-
657
- private resetKnownStateCache() {
658
- this._cachedKnownState = undefined;
659
- this._cachedKnownStateWithStreaming = undefined;
630
+ this.processNewTransactions();
631
+ this.scheduleNotifyUpdate();
632
+ } catch (e) {
633
+ return { type: "InvalidSignature", id: this.id, error: e } as const;
634
+ }
660
635
  }
661
636
 
662
637
  private processNewTransactions() {
@@ -805,7 +780,6 @@ export class CoValueCore {
805
780
 
806
781
  this.node.syncManager.recordTransactionsSize([transaction], "local");
807
782
 
808
- this.resetKnownStateCache();
809
783
  this.processNewTransactions();
810
784
  this.addDependenciesFromNewTransaction(transaction);
811
785
 
@@ -1,4 +1,3 @@
1
- import { Result, err, ok } from "neverthrow";
2
1
  import { AnyRawCoValue } from "../coValue.js";
3
2
  import {
4
3
  createContentMessage,
@@ -94,16 +93,14 @@ export class VerifiedState {
94
93
  newTransactions: Transaction[],
95
94
  newSignature: Signature,
96
95
  skipVerify: boolean = false,
97
- ): Result<true, TryAddTransactionsError> {
98
- const result = this.sessions.addTransaction(
96
+ ) {
97
+ this.sessions.addTransaction(
99
98
  sessionID,
100
99
  signerID,
101
100
  newTransactions,
102
101
  newSignature,
103
102
  skipVerify,
104
103
  );
105
-
106
- return result;
107
104
  }
108
105
 
109
106
  makeNewTrustingTransaction(
@@ -293,16 +290,18 @@ export class VerifiedState {
293
290
  );
294
291
 
295
292
  if (piecesWithContent.length > 1 || this.isStreaming()) {
296
- // Flag that more content is coming
297
- if (knownState) {
298
- firstPiece.expectContentUntil = getKnownStateToSend(
299
- this.knownStateWithStreaming().sessions,
300
- knownState.sessions,
301
- );
302
- } else {
303
- firstPiece.expectContentUntil = {
304
- ...this.knownStateWithStreaming().sessions,
305
- };
293
+ if (!opts?.skipExpectContentUntil) {
294
+ // Flag that more content is coming
295
+ if (knownState) {
296
+ firstPiece.expectContentUntil = getKnownStateToSend(
297
+ this.knownStateWithStreaming().sessions,
298
+ knownState.sessions,
299
+ );
300
+ } else {
301
+ firstPiece.expectContentUntil = {
302
+ ...this.knownStateWithStreaming().sessions,
303
+ };
304
+ }
306
305
  }
307
306
  }
308
307
 
@@ -321,6 +320,17 @@ export class VerifiedState {
321
320
  return this.sessions.knownStateWithStreaming ?? this.knownState();
322
321
  }
323
322
 
323
+ immutableKnownState() {
324
+ return this.sessions.immutableKnownState;
325
+ }
326
+
327
+ immutableKnownStateWithStreaming() {
328
+ return (
329
+ this.sessions.immutableKnownStateWithStreaming ??
330
+ this.immutableKnownState()
331
+ );
332
+ }
333
+
324
334
  isStreaming(): boolean {
325
335
  return Boolean(this.sessions.knownStateWithStreaming);
326
336
  }
@@ -206,7 +206,7 @@ export class RawGroup<
206
206
  }
207
207
 
208
208
  const runMigrations = () => {
209
- rotateReadKeyIfNeeded(this);
209
+ // rotateReadKeyIfNeeded(this);
210
210
  healMissingKeyForEveryone(this);
211
211
  };
212
212
 
@@ -508,12 +508,14 @@ export class RawGroup<
508
508
  memberRole === "writerInvite" ||
509
509
  memberRole === "adminInvite"
510
510
  ) {
511
- const otherMemberAgent = this.core.node
512
- .resolveAccountAgent(
513
- otherMemberKey,
514
- "Expected member agent to be loaded",
515
- )
516
- ._unsafeUnwrap({ withStackTrace: true });
511
+ const otherMemberAgent = this.core.node.resolveAccountAgent(
512
+ otherMemberKey,
513
+ "Expected member agent to be loaded",
514
+ ).value;
515
+
516
+ if (!otherMemberAgent) {
517
+ throw new Error("Expected member agent to be loaded");
518
+ }
517
519
 
518
520
  this.storeKeyRevelationForMember(
519
521
  otherMemberKey,
@@ -689,9 +691,14 @@ export class RawGroup<
689
691
 
690
692
  if (lastReadyKeyEdit?.value) {
691
693
  const revealer = lastReadyKeyEdit.by;
692
- const revealerAgent = core.node
693
- .resolveAccountAgent(revealer, "Expected to know revealer")
694
- ._unsafeUnwrap({ withStackTrace: true });
694
+ const revealerAgent = core.node.resolveAccountAgent(
695
+ revealer,
696
+ "Expected to know revealer",
697
+ ).value;
698
+
699
+ if (!revealerAgent) {
700
+ throw new Error("Expected to know revealer");
701
+ }
695
702
 
696
703
  const secret = this.crypto.unseal(
697
704
  lastReadyKeyEdit.value,
@@ -841,12 +848,14 @@ export class RawGroup<
841
848
  const newReadKey = this.crypto.newRandomKeySecret();
842
849
 
843
850
  for (const readerID of currentlyPermittedReaders) {
844
- const agent = this.core.node
845
- .resolveAccountAgent(
846
- readerID,
847
- "Expected to know currently permitted reader",
848
- )
849
- ._unsafeUnwrap({ withStackTrace: true });
851
+ const agent = this.core.node.resolveAccountAgent(
852
+ readerID,
853
+ "Expected to know currently permitted reader",
854
+ ).value;
855
+
856
+ if (!agent) {
857
+ throw new Error("Expected to know currently permitted reader");
858
+ }
850
859
 
851
860
  this.storeKeyRevelationForMember(
852
861
  readerID,
@@ -861,12 +870,14 @@ export class RawGroup<
861
870
  * and reveal them to the other non-writeOnly members
862
871
  */
863
872
  for (const writeOnlyMemberID of writeOnlyMembers) {
864
- const agent = this.core.node
865
- .resolveAccountAgent(
866
- writeOnlyMemberID,
867
- "Expected to know writeOnly member",
868
- )
869
- ._unsafeUnwrap({ withStackTrace: true });
873
+ const agent = this.core.node.resolveAccountAgent(
874
+ writeOnlyMemberID,
875
+ "Expected to know writeOnly member",
876
+ ).value;
877
+
878
+ if (!agent) {
879
+ throw new Error("Expected to know writeOnly member");
880
+ }
870
881
 
871
882
  const writeOnlyKey = this.crypto.newRandomKeySecret();
872
883
 
@@ -879,12 +890,14 @@ export class RawGroup<
879
890
  this.set(`writeKeyFor_${writeOnlyMemberID}`, writeOnlyKey.id, "trusting");
880
891
 
881
892
  for (const readerID of currentlyPermittedReaders) {
882
- const agent = this.core.node
883
- .resolveAccountAgent(
884
- readerID,
885
- "Expected to know currently permitted reader",
886
- )
887
- ._unsafeUnwrap({ withStackTrace: true });
893
+ const agent = this.core.node.resolveAccountAgent(
894
+ readerID,
895
+ "Expected to know currently permitted reader",
896
+ ).value;
897
+
898
+ if (!agent) {
899
+ throw new Error("Expected to know currently permitted reader");
900
+ }
888
901
 
889
902
  this.storeKeyRevelationForMember(
890
903
  readerID,
package/src/knownState.ts CHANGED
@@ -113,10 +113,7 @@ export function areCurrentSessionsInSyncWith(
113
113
  current: Record<string, number>,
114
114
  target: Record<string, number>,
115
115
  ) {
116
- for (const [sessionId, currentCount] of Object.entries(current) as [
117
- SessionID,
118
- number,
119
- ][]) {
116
+ for (const [sessionId, currentCount] of Object.entries(current)) {
120
117
  const targetCount = target[sessionId] ?? 0;
121
118
  if (currentCount !== targetCount) {
122
119
  return false;
@@ -133,10 +130,7 @@ export function isKnownStateSubsetOf(
133
130
  current: Record<string, number>,
134
131
  target: Record<string, number>,
135
132
  ) {
136
- for (const [sessionId, currentCount] of Object.entries(current) as [
137
- SessionID,
138
- number,
139
- ][]) {
133
+ for (const [sessionId, currentCount] of Object.entries(current)) {
140
134
  const targetCount = target[sessionId] ?? 0;
141
135
  if (currentCount > targetCount) {
142
136
  return false;
@@ -154,10 +148,7 @@ export function getKnownStateToSend(
154
148
  target: Record<string, number>,
155
149
  ) {
156
150
  const toSend: Record<string, number> = {};
157
- for (const [sessionId, currentCount] of Object.entries(current) as [
158
- SessionID,
159
- number,
160
- ][]) {
151
+ for (const [sessionId, currentCount] of Object.entries(current)) {
161
152
  const targetCount = target[sessionId] ?? 0;
162
153
  if (currentCount > targetCount) {
163
154
  toSend[sessionId] = currentCount;
package/src/localNode.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Result, err, ok } from "neverthrow";
2
1
  import { GarbageCollector } from "./GarbageCollector.js";
3
2
  import type { CoID } from "./coValue.js";
4
3
  import type { RawCoValue } from "./coValue.js";
@@ -689,12 +688,9 @@ export class LocalNode {
689
688
  }
690
689
 
691
690
  /** @internal */
692
- resolveAccountAgent(
693
- id: RawAccountID | AgentID,
694
- expectation?: string,
695
- ): Result<AgentID, ResolveAccountAgentError> {
691
+ resolveAccountAgent(id: RawAccountID | AgentID, expectation?: string) {
696
692
  if (isAgentID(id)) {
697
- return ok(id);
693
+ return { value: id, error: undefined };
698
694
  }
699
695
 
700
696
  let coValue: AvailableCoValueCore;
@@ -702,12 +698,7 @@ export class LocalNode {
702
698
  try {
703
699
  coValue = this.expectCoValueLoaded(id, expectation);
704
700
  } catch (e) {
705
- return err({
706
- type: "ErrorLoadingCoValueCore",
707
- expectation,
708
- id,
709
- error: e,
710
- } satisfies LoadCoValueCoreError);
701
+ return { value: undefined, error: e };
711
702
  }
712
703
 
713
704
  if (
@@ -717,14 +708,15 @@ export class LocalNode {
717
708
  !("type" in coValue.verified.header.meta) ||
718
709
  coValue.verified.header.meta.type !== "account"
719
710
  ) {
720
- return err({
721
- type: "UnexpectedlyNotAccount",
722
- expectation,
723
- id,
724
- } satisfies UnexpectedlyNotAccountError);
711
+ return {
712
+ value: undefined,
713
+ error: new Error(`Unexpectedly not account: ${expectation}`),
714
+ };
725
715
  }
726
716
 
727
- return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
717
+ const account = coValue.getCurrentContent() as RawAccount;
718
+
719
+ return { value: account.currentAgentID(), error: undefined };
728
720
  }
729
721
 
730
722
  createGroup(
package/src/sync.ts CHANGED
@@ -4,6 +4,8 @@ import { PeerState } from "./PeerState.js";
4
4
  import { SyncStateManager } from "./SyncStateManager.js";
5
5
  import {
6
6
  getContenDebugInfo,
7
+ getNewTransactionsFromContentMessage,
8
+ getSessionEntriesFromContentMessage,
7
9
  getTransactionSize,
8
10
  knownStateFromContent,
9
11
  } from "./coValueContentMessage.js";
@@ -609,56 +611,55 @@ export class SyncManager {
609
611
  /**
610
612
  * The coValue is in memory, load the transactions from the content message
611
613
  */
612
- for (const [sessionID, newContentForSession] of Object.entries(msg.new) as [
613
- SessionID,
614
- SessionNewContent,
615
- ][]) {
616
- const ourKnownTxIdx =
617
- coValue.verified.sessions.get(sessionID)?.transactions.length;
618
- const theirFirstNewTxIdx = newContentForSession.after;
619
-
620
- if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
614
+ for (const [
615
+ sessionID,
616
+ newContentForSession,
617
+ ] of getSessionEntriesFromContentMessage(msg)) {
618
+ const newTransactions = getNewTransactionsFromContentMessage(
619
+ newContentForSession,
620
+ coValue.knownState(),
621
+ sessionID,
622
+ );
623
+
624
+ if (newTransactions === undefined) {
621
625
  invalidStateAssumed = true;
622
626
  continue;
623
627
  }
624
628
 
625
- const alreadyKnownOffset = ourKnownTxIdx
626
- ? ourKnownTxIdx - theirFirstNewTxIdx
627
- : 0;
628
-
629
- const newTransactions =
630
- newContentForSession.newTransactions.slice(alreadyKnownOffset);
631
-
632
629
  if (newTransactions.length === 0) {
633
630
  continue;
634
631
  }
635
632
 
636
633
  // TODO: Handle invalid signatures in the middle of streaming
637
634
  // This could cause a situation where we are unable to load a chunk, and ask for a correction for all the subsequent chunks
638
- const result = coValue.tryAddTransactions(
635
+ const error = coValue.tryAddTransactions(
639
636
  sessionID,
640
637
  newTransactions,
641
638
  newContentForSession.lastSignature,
642
639
  this.skipVerify,
643
640
  );
644
641
 
645
- if (result.isErr()) {
642
+ if (error) {
646
643
  if (peer) {
647
644
  logger.error("Failed to add transactions", {
648
645
  peerId: peer.id,
649
646
  peerRole: peer.role,
650
647
  id: msg.id,
651
- err: result.error,
648
+ errorType: error.type,
649
+ err: error.error,
650
+ sessionID,
652
651
  msgKnownState: knownStateFromContent(msg).sessions,
652
+ msgSummary: getContenDebugInfo(msg),
653
653
  knownState: coValue.knownState().sessions,
654
- newContent: validNewContent.new,
655
654
  });
656
655
  // TODO Mark only the session as errored, not the whole coValue
657
- coValue.markErrored(peer.id, result.error);
656
+ coValue.markErrored(peer.id, error);
658
657
  } else {
659
658
  logger.error("Failed to add transactions from storage", {
660
659
  id: msg.id,
661
- err: result.error,
660
+ err: error.error,
661
+ sessionID,
662
+ errorType: error.type,
662
663
  });
663
664
  }
664
665
  continue;
@@ -669,9 +670,7 @@ export class SyncManager {
669
670
  }
670
671
 
671
672
  // The new content for this session has been verified, so we can store it
672
- if (result.value) {
673
- validNewContent.new[sessionID] = newContentForSession;
674
- }
673
+ validNewContent.new[sessionID] = newContentForSession;
675
674
  }
676
675
 
677
676
  if (peer) {