cojson 0.16.4 → 0.16.6

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 (150) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/dist/GarbageCollector.d.ts +12 -0
  4. package/dist/GarbageCollector.d.ts.map +1 -0
  5. package/dist/GarbageCollector.js +37 -0
  6. package/dist/GarbageCollector.js.map +1 -0
  7. package/dist/coValue.d.ts +1 -1
  8. package/dist/coValue.d.ts.map +1 -1
  9. package/dist/coValue.js.map +1 -1
  10. package/dist/coValueContentMessage.d.ts +1 -0
  11. package/dist/coValueContentMessage.d.ts.map +1 -1
  12. package/dist/coValueContentMessage.js +11 -3
  13. package/dist/coValueContentMessage.js.map +1 -1
  14. package/dist/coValueCore/coValueCore.d.ts +8 -11
  15. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  16. package/dist/coValueCore/coValueCore.js +30 -122
  17. package/dist/coValueCore/coValueCore.js.map +1 -1
  18. package/dist/coValueCore/utils.d.ts.map +1 -1
  19. package/dist/coValueCore/utils.js.map +1 -1
  20. package/dist/coValueCore/verifiedState.d.ts +1 -0
  21. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  22. package/dist/coValueCore/verifiedState.js.map +1 -1
  23. package/dist/coValues/coMap.d.ts +3 -3
  24. package/dist/coValues/coPlainText.d.ts +1 -0
  25. package/dist/coValues/coPlainText.d.ts.map +1 -1
  26. package/dist/coValues/coPlainText.js +27 -8
  27. package/dist/coValues/coPlainText.js.map +1 -1
  28. package/dist/coValues/coStream.d.ts +2 -2
  29. package/dist/coValues/group.d.ts +19 -11
  30. package/dist/coValues/group.d.ts.map +1 -1
  31. package/dist/coValues/group.js +221 -59
  32. package/dist/coValues/group.js.map +1 -1
  33. package/dist/config.d.ts +10 -1
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +16 -1
  36. package/dist/config.js.map +1 -1
  37. package/dist/exports.d.ts +11 -4
  38. package/dist/exports.d.ts.map +1 -1
  39. package/dist/exports.js +8 -3
  40. package/dist/exports.js.map +1 -1
  41. package/dist/ids.d.ts +3 -3
  42. package/dist/ids.d.ts.map +1 -1
  43. package/dist/ids.js.map +1 -1
  44. package/dist/localNode.d.ts +8 -5
  45. package/dist/localNode.d.ts.map +1 -1
  46. package/dist/localNode.js +11 -0
  47. package/dist/localNode.js.map +1 -1
  48. package/dist/queue/LocalTransactionsSyncQueue.js +1 -1
  49. package/dist/queue/LocalTransactionsSyncQueue.js.map +1 -1
  50. package/dist/queue/StoreQueue.d.ts +7 -1
  51. package/dist/queue/StoreQueue.d.ts.map +1 -1
  52. package/dist/queue/StoreQueue.js +35 -13
  53. package/dist/queue/StoreQueue.js.map +1 -1
  54. package/dist/storage/sqlite/client.d.ts +4 -4
  55. package/dist/storage/sqlite/client.d.ts.map +1 -1
  56. package/dist/storage/sqlite/client.js +13 -4
  57. package/dist/storage/sqlite/client.js.map +1 -1
  58. package/dist/storage/sqliteAsync/client.d.ts +3 -3
  59. package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
  60. package/dist/storage/sqliteAsync/client.js +12 -3
  61. package/dist/storage/sqliteAsync/client.js.map +1 -1
  62. package/dist/storage/storageAsync.d.ts.map +1 -1
  63. package/dist/storage/storageAsync.js +2 -7
  64. package/dist/storage/storageAsync.js.map +1 -1
  65. package/dist/storage/storageSync.d.ts.map +1 -1
  66. package/dist/storage/storageSync.js +2 -7
  67. package/dist/storage/storageSync.js.map +1 -1
  68. package/dist/storage/types.d.ts +2 -2
  69. package/dist/storage/types.d.ts.map +1 -1
  70. package/dist/sync.d.ts.map +1 -1
  71. package/dist/sync.js +17 -3
  72. package/dist/sync.js.map +1 -1
  73. package/dist/tests/GarbageCollector.test.d.ts +2 -0
  74. package/dist/tests/GarbageCollector.test.d.ts.map +1 -0
  75. package/dist/tests/GarbageCollector.test.js +85 -0
  76. package/dist/tests/GarbageCollector.test.js.map +1 -0
  77. package/dist/tests/coPlainText.test.js +142 -4
  78. package/dist/tests/coPlainText.test.js.map +1 -1
  79. package/dist/tests/coStream.test.js +3 -3
  80. package/dist/tests/coStream.test.js.map +1 -1
  81. package/dist/tests/group.inheritance.test.js +195 -0
  82. package/dist/tests/group.inheritance.test.js.map +1 -1
  83. package/dist/tests/group.removeMember.test.js +152 -1
  84. package/dist/tests/group.removeMember.test.js.map +1 -1
  85. package/dist/tests/group.roleOf.test.js +2 -2
  86. package/dist/tests/group.roleOf.test.js.map +1 -1
  87. package/dist/tests/group.test.js +81 -3
  88. package/dist/tests/group.test.js.map +1 -1
  89. package/dist/tests/sync.garbageCollection.test.d.ts +2 -0
  90. package/dist/tests/sync.garbageCollection.test.d.ts.map +1 -0
  91. package/dist/tests/sync.garbageCollection.test.js +133 -0
  92. package/dist/tests/sync.garbageCollection.test.js.map +1 -0
  93. package/dist/tests/sync.load.test.js +6 -3
  94. package/dist/tests/sync.load.test.js.map +1 -1
  95. package/dist/tests/sync.mesh.test.js +48 -34
  96. package/dist/tests/sync.mesh.test.js.map +1 -1
  97. package/dist/tests/sync.storage.test.js +31 -21
  98. package/dist/tests/sync.storage.test.js.map +1 -1
  99. package/dist/tests/sync.storageAsync.test.js +76 -29
  100. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  101. package/dist/tests/testStorage.d.ts +1 -0
  102. package/dist/tests/testStorage.d.ts.map +1 -1
  103. package/dist/tests/testStorage.js +1 -1
  104. package/dist/tests/testStorage.js.map +1 -1
  105. package/dist/tests/testUtils.d.ts +2 -0
  106. package/dist/tests/testUtils.d.ts.map +1 -1
  107. package/dist/tests/testUtils.js +6 -0
  108. package/dist/tests/testUtils.js.map +1 -1
  109. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts +2 -2
  110. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts.map +1 -1
  111. package/dist/typeUtils/expectGroup.d.ts.map +1 -1
  112. package/dist/typeUtils/expectGroup.js +6 -5
  113. package/dist/typeUtils/expectGroup.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/GarbageCollector.ts +48 -0
  116. package/src/coValue.ts +1 -4
  117. package/src/coValueContentMessage.ts +16 -3
  118. package/src/coValueCore/coValueCore.ts +50 -198
  119. package/src/coValueCore/utils.ts +1 -0
  120. package/src/coValueCore/verifiedState.ts +1 -0
  121. package/src/coValues/coPlainText.ts +40 -8
  122. package/src/coValues/group.ts +310 -91
  123. package/src/config.ts +20 -1
  124. package/src/exports.ts +13 -5
  125. package/src/ids.ts +3 -3
  126. package/src/localNode.ts +22 -8
  127. package/src/queue/LocalTransactionsSyncQueue.ts +1 -1
  128. package/src/queue/StoreQueue.ts +45 -12
  129. package/src/storage/sqlite/client.ts +24 -10
  130. package/src/storage/sqliteAsync/client.ts +26 -5
  131. package/src/storage/storageAsync.ts +5 -9
  132. package/src/storage/storageSync.ts +2 -9
  133. package/src/storage/types.ts +7 -4
  134. package/src/sync.ts +19 -3
  135. package/src/tests/GarbageCollector.test.ts +127 -0
  136. package/src/tests/coPlainText.test.ts +176 -4
  137. package/src/tests/coStream.test.ts +7 -3
  138. package/src/tests/group.inheritance.test.ts +279 -0
  139. package/src/tests/group.removeMember.test.ts +244 -1
  140. package/src/tests/group.roleOf.test.ts +2 -2
  141. package/src/tests/group.test.ts +105 -5
  142. package/src/tests/sync.garbageCollection.test.ts +178 -0
  143. package/src/tests/sync.load.test.ts +6 -3
  144. package/src/tests/sync.mesh.test.ts +49 -34
  145. package/src/tests/sync.storage.test.ts +31 -21
  146. package/src/tests/sync.storageAsync.test.ts +81 -29
  147. package/src/tests/testStorage.ts +11 -3
  148. package/src/tests/testUtils.ts +9 -1
  149. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +2 -2
  150. package/src/typeUtils/expectGroup.ts +8 -5
@@ -1,9 +1,9 @@
1
1
  import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
2
2
  import { Result, err } from "neverthrow";
3
- import { PeerState } from "../PeerState.js";
4
- import { RawCoValue } from "../coValue.js";
5
- import { ControlledAccountOrAgent } from "../coValues/account.js";
6
- import { RawGroup } from "../coValues/group.js";
3
+ import type { PeerState } from "../PeerState.js";
4
+ import type { RawCoValue } from "../coValue.js";
5
+ import type { ControlledAccountOrAgent } from "../coValues/account.js";
6
+ import type { RawGroup } from "../coValues/group.js";
7
7
  import { CO_VALUE_LOADING_CONFIG } from "../config.js";
8
8
  import { coreToCoValue } from "../coreToCoValue.js";
9
9
  import {
@@ -16,25 +16,15 @@ import {
16
16
  SignerID,
17
17
  StreamingHash,
18
18
  } from "../crypto/crypto.js";
19
- import {
20
- RawCoID,
21
- SessionID,
22
- TransactionID,
23
- getParentGroupId,
24
- isParentGroupReference,
25
- } from "../ids.js";
19
+ import { RawCoID, SessionID, TransactionID } from "../ids.js";
26
20
  import { parseJSON, stableStringify } from "../jsonStringify.js";
27
21
  import { JsonValue } from "../jsonValue.js";
28
22
  import { LocalNode, ResolveAccountAgentError } from "../localNode.js";
29
23
  import { logger } from "../logger.js";
30
- import {
31
- determineValidTransactions,
32
- isKeyForKeyField,
33
- } from "../permissions.js";
24
+ import { determineValidTransactions } from "../permissions.js";
34
25
  import { CoValueKnownState, PeerID, emptyKnownState } from "../sync.js";
35
26
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
36
27
  import { expectGroup } from "../typeUtils/expectGroup.js";
37
- import { isAccountID } from "../typeUtils/isAccountID.js";
38
28
  import { getDependedOnCoValuesFromRawData } from "./utils.js";
39
29
  import { CoValueHeader, Transaction, VerifiedState } from "./verifiedState.js";
40
30
 
@@ -53,8 +43,6 @@ export type DecryptedTransaction = {
53
43
  trusting?: boolean;
54
44
  };
55
45
 
56
- const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
57
-
58
46
  export type AvailableCoValueCore = CoValueCore & { verified: VerifiedState };
59
47
 
60
48
  export class CoValueCore {
@@ -81,7 +69,9 @@ export class CoValueCore {
81
69
  }
82
70
  private readonly peers = new Map<
83
71
  PeerID,
84
- | { type: "unknown" | "pending" | "available" | "unavailable" }
72
+ | {
73
+ type: "unknown" | "pending" | "available" | "unavailable";
74
+ }
85
75
  | {
86
76
  type: "errored";
87
77
  error: TryAddTransactionsError;
@@ -90,9 +80,8 @@ export class CoValueCore {
90
80
 
91
81
  // cached state and listeners
92
82
  private _cachedContent?: RawCoValue;
93
- private readonly listeners: Set<
94
- (core: CoValueCore, unsub: () => void) => void
95
- > = new Set();
83
+ readonly listeners: Set<(core: CoValueCore, unsub: () => void) => void> =
84
+ new Set();
96
85
  private readonly _decryptionCache: {
97
86
  [key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
98
87
  } = {};
@@ -213,6 +202,26 @@ export class CoValueCore {
213
202
  }
214
203
  }
215
204
 
205
+ unmount() {
206
+ // For simplicity, we don't unmount groups and accounts
207
+ if (this.verified?.header.ruleset.type === "group") {
208
+ return false;
209
+ }
210
+
211
+ if (this.listeners.size > 0) {
212
+ return false; // The coValue is still in use
213
+ }
214
+
215
+ this.counter.add(-1, { state: this.loadingState });
216
+
217
+ if (this.groupInvalidationSubscription) {
218
+ this.groupInvalidationSubscription();
219
+ this.groupInvalidationSubscription = undefined;
220
+ }
221
+
222
+ return true;
223
+ }
224
+
216
225
  markNotFoundInPeer(peerId: PeerID) {
217
226
  const previousState = this.loadingState;
218
227
  this.peers.set(peerId, { type: "unavailable" });
@@ -621,9 +630,7 @@ export class CoValueCore {
621
630
  return success;
622
631
  }
623
632
 
624
- getCurrentContent(options?: {
625
- ignorePrivateTransactions: true;
626
- }): RawCoValue {
633
+ getCurrentContent(options?: { ignorePrivateTransactions: true }): RawCoValue {
627
634
  if (!this.verified) {
628
635
  throw new Error(
629
636
  "CoValueCore: getCurrentContent called on coValue without verified state",
@@ -768,20 +775,7 @@ export class CoValueCore {
768
775
  }
769
776
 
770
777
  if (this.verified.header.ruleset.type === "group") {
771
- const content = expectGroup(this.getCurrentContent());
772
-
773
- const currentKeyId = content.getCurrentReadKeyId();
774
-
775
- if (!currentKeyId) {
776
- throw new Error("No readKey set");
777
- }
778
-
779
- const secret = this.getReadKey(currentKeyId);
780
-
781
- return {
782
- secret: secret,
783
- id: currentKeyId,
784
- };
778
+ return expectGroup(this.getCurrentContent()).getCurrentReadKey();
785
779
  } else if (this.verified.header.ruleset.type === "ownedByGroup") {
786
780
  return this.node
787
781
  .expectCoValueLoaded(this.verified.header.ruleset.group)
@@ -793,154 +787,36 @@ export class CoValueCore {
793
787
  }
794
788
  }
795
789
 
790
+ readKeyCache = new Map<KeyID, KeySecret>();
796
791
  getReadKey(keyID: KeyID): KeySecret | undefined {
797
- let key = readKeyCache.get(this)?.[keyID];
798
- if (!key) {
799
- key = this.getUncachedReadKey(keyID);
800
- if (key) {
801
- let cache = readKeyCache.get(this);
802
- if (!cache) {
803
- cache = {};
804
- readKeyCache.set(this, cache);
805
- }
806
- cache[keyID] = key;
807
- }
792
+ // We want to check the cache here, to skip re-computing the group content
793
+ const cachedSecret = this.readKeyCache.get(keyID);
794
+
795
+ if (cachedSecret) {
796
+ return cachedSecret;
808
797
  }
809
- return key;
810
- }
811
798
 
812
- getUncachedReadKey(keyID: KeyID): KeySecret | undefined {
813
799
  if (!this.verified) {
814
800
  throw new Error(
815
801
  "CoValueCore: getUncachedReadKey called on coValue without verified state",
816
802
  );
817
803
  }
818
804
 
805
+ // Getting the readKey from accounts
819
806
  if (this.verified.header.ruleset.type === "group") {
820
807
  const content = expectGroup(
821
- this.getCurrentContent({ ignorePrivateTransactions: true }), // to prevent recursion
822
- );
823
- const keyForEveryone = content.get(`${keyID}_for_everyone`);
824
- if (keyForEveryone) {
825
- return keyForEveryone;
826
- }
827
-
828
- // Try to find key revelation for us
829
- const currentAgentOrAccountID = accountOrAgentIDfromSessionID(
830
- this.node.currentSessionID,
831
- );
832
-
833
- // being careful here to avoid recursion
834
- const lookupAccountOrAgentID = isAccountID(currentAgentOrAccountID)
835
- ? this.id === currentAgentOrAccountID
836
- ? this.crypto.getAgentID(this.node.agentSecret) // in accounts, the read key is revealed for the primitive agent
837
- : currentAgentOrAccountID // current account ID
838
- : currentAgentOrAccountID; // current agent ID
839
-
840
- const lastReadyKeyEdit = content.lastEditAt(
841
- `${keyID}_for_${lookupAccountOrAgentID}`,
808
+ // load the account without private transactions, because we are here
809
+ // to be able to decrypt those
810
+ this.getCurrentContent({ ignorePrivateTransactions: true }),
842
811
  );
843
812
 
844
- if (lastReadyKeyEdit?.value) {
845
- const revealer = lastReadyKeyEdit.by;
846
- const revealerAgent = this.node
847
- .resolveAccountAgent(revealer, "Expected to know revealer")
848
- ._unsafeUnwrap({ withStackTrace: true });
849
-
850
- const secret = this.crypto.unseal(
851
- lastReadyKeyEdit.value,
852
- this.crypto.getAgentSealerSecret(this.node.agentSecret), // being careful here to avoid recursion
853
- this.crypto.getAgentSealerID(revealerAgent),
854
- {
855
- in: this.id,
856
- tx: lastReadyKeyEdit.tx,
857
- },
858
- );
859
-
860
- if (secret) {
861
- return secret as KeySecret;
862
- }
863
- }
864
-
865
- // Try to find indirect revelation through previousKeys
866
-
867
- for (const co of content.keys()) {
868
- if (isKeyForKeyField(co) && co.startsWith(keyID)) {
869
- const encryptingKeyID = co.split("_for_")[1] as KeyID;
870
- const encryptingKeySecret = this.getReadKey(encryptingKeyID);
871
-
872
- if (!encryptingKeySecret) {
873
- continue;
874
- }
875
-
876
- const encryptedPreviousKey = content.get(co)!;
877
-
878
- const secret = this.crypto.decryptKeySecret(
879
- {
880
- encryptedID: keyID,
881
- encryptingID: encryptingKeyID,
882
- encrypted: encryptedPreviousKey,
883
- },
884
- encryptingKeySecret,
885
- );
886
-
887
- if (secret) {
888
- return secret as KeySecret;
889
- } else {
890
- logger.warn(
891
- `Encrypting ${encryptingKeyID} key didn't decrypt ${keyID}`,
892
- );
893
- }
894
- }
895
- }
896
-
897
- // try to find revelation to parent group read keys
898
- for (const co of content.keys()) {
899
- if (isParentGroupReference(co)) {
900
- const parentGroupID = getParentGroupId(co);
901
- const parentGroup = this.node.expectCoValueLoaded(
902
- parentGroupID,
903
- "Expected parent group to be loaded",
904
- );
905
-
906
- const parentKeys = this.findValidParentKeys(
907
- keyID,
908
- content,
909
- parentGroup,
910
- );
911
-
912
- for (const parentKey of parentKeys) {
913
- const revelationForParentKey = content.get(
914
- `${keyID}_for_${parentKey.id}`,
915
- );
916
-
917
- if (revelationForParentKey) {
918
- const secret = parentGroup.crypto.decryptKeySecret(
919
- {
920
- encryptedID: keyID,
921
- encryptingID: parentKey.id,
922
- encrypted: revelationForParentKey,
923
- },
924
- parentKey.secret,
925
- );
926
-
927
- if (secret) {
928
- return secret as KeySecret;
929
- } else {
930
- logger.warn(
931
- `Encrypting parent ${parentKey.id} key didn't decrypt ${keyID}`,
932
- );
933
- }
934
- }
935
- }
936
- }
937
- }
938
-
939
- return undefined;
813
+ return content.getReadKey(keyID);
940
814
  } else if (this.verified.header.ruleset.type === "ownedByGroup") {
941
- return this.node
942
- .expectCoValueLoaded(this.verified.header.ruleset.group)
943
- .getReadKey(keyID);
815
+ return expectGroup(
816
+ this.node
817
+ .expectCoValueLoaded(this.verified.header.ruleset.group)
818
+ .getCurrentContent(),
819
+ ).getReadKey(keyID);
944
820
  } else {
945
821
  throw new Error(
946
822
  "Only groups or values owned by groups have read secrets",
@@ -948,28 +824,6 @@ export class CoValueCore {
948
824
  }
949
825
  }
950
826
 
951
- findValidParentKeys(keyID: KeyID, group: RawGroup, parentGroup: CoValueCore) {
952
- const validParentKeys: { id: KeyID; secret: KeySecret }[] = [];
953
-
954
- for (const co of group.keys()) {
955
- if (isKeyForKeyField(co) && co.startsWith(keyID)) {
956
- const encryptingKeyID = co.split("_for_")[1] as KeyID;
957
- const encryptingKeySecret = parentGroup.getReadKey(encryptingKeyID);
958
-
959
- if (!encryptingKeySecret) {
960
- continue;
961
- }
962
-
963
- validParentKeys.push({
964
- id: encryptingKeyID,
965
- secret: encryptingKeySecret,
966
- });
967
- }
968
- }
969
-
970
- return validParentKeys;
971
- }
972
-
973
827
  getGroup(): RawGroup {
974
828
  if (!this.verified) {
975
829
  throw new Error(
@@ -1016,9 +870,7 @@ export class CoValueCore {
1016
870
  }
1017
871
  }
1018
872
 
1019
- waitForSync(options?: {
1020
- timeout?: number;
1021
- }) {
873
+ waitForSync(options?: { timeout?: number }) {
1022
874
  return this.node.syncManager.waitForSync(this.id, options?.timeout);
1023
875
  }
1024
876
 
@@ -2,6 +2,7 @@ import { getGroupDependentKey } from "../ids.js";
2
2
  import { RawCoID, SessionID } from "../ids.js";
3
3
  import { Stringified, parseJSON } from "../jsonStringify.js";
4
4
  import { JsonValue } from "../jsonValue.js";
5
+ import { NewContentMessage } from "../sync.js";
5
6
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
6
7
  import { isAccountID } from "../typeUtils/isAccountID.js";
7
8
  import { CoValueHeader, Transaction } from "./verifiedState.js";
@@ -65,6 +65,7 @@ export class VerifiedState {
65
65
  private _cachedKnownState?: CoValueKnownState;
66
66
  private _cachedNewContentSinceEmpty: NewContentMessage[] | undefined;
67
67
  private streamingKnownState?: CoValueKnownState["sessions"];
68
+ public lastAccessed: number | undefined;
68
69
 
69
70
  constructor(
70
71
  id: RawCoID,
@@ -1,5 +1,6 @@
1
1
  import { splitGraphemes } from "unicode-segmenter/grapheme";
2
2
  import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
3
+ import { TRANSACTION_CONFIG } from "../config.js";
3
4
  import { JsonObject } from "../jsonValue.js";
4
5
  import { DeletionOpPayload, OpID, RawCoList } from "./coList.js";
5
6
 
@@ -110,16 +111,34 @@ export class RawCoPlainText<
110
111
  text: string,
111
112
  privacy: "private" | "trusting" = "private",
112
113
  ) {
113
- const graphemes = [...splitGraphemes(text)];
114
+ const graphemes = Array.from(splitGraphemes(text));
114
115
 
115
116
  if (idx === 0) {
116
- // For insertions at start, prepend each character in reverse
117
- for (const grapheme of graphemes.reverse()) {
118
- this.prepend(grapheme, 0, privacy);
117
+ // For insertions at start, prepend the first char and append the rest
118
+ const firstChar = graphemes[0];
119
+
120
+ if (firstChar) {
121
+ this.prepend(firstChar, 0, privacy);
122
+ }
123
+
124
+ if (graphemes.length > 1) {
125
+ this.appendChars(graphemes.slice(1), 0, privacy);
119
126
  }
120
127
  } else {
121
128
  // For other insertions, append after the previous character
122
- this.appendItems(graphemes, idx - 1, privacy);
129
+ this.appendChars(graphemes, idx - 1, privacy);
130
+ }
131
+ }
132
+
133
+ appendChars(
134
+ text: string[],
135
+ position: number,
136
+ privacy: "private" | "trusting" = "private",
137
+ ) {
138
+ const chunks = splitIntoChunks(text);
139
+ for (const chunk of chunks) {
140
+ this.appendItems(chunk, position, privacy);
141
+ position += chunk.length;
123
142
  }
124
143
  }
125
144
 
@@ -136,11 +155,12 @@ export class RawCoPlainText<
136
155
  text: string,
137
156
  privacy: "private" | "trusting" = "private",
138
157
  ) {
139
- const graphemes = [...splitGraphemes(text)];
158
+ const graphemes = Array.from(splitGraphemes(text));
159
+
140
160
  if (idx >= this.entries().length) {
141
- this.appendItems(graphemes, idx - 1, privacy);
161
+ this.appendChars(graphemes, idx - 1, privacy);
142
162
  } else {
143
- this.appendItems(graphemes, idx, privacy);
163
+ this.appendChars(graphemes, idx, privacy);
144
164
  }
145
165
  }
146
166
 
@@ -178,3 +198,15 @@ export class RawCoPlainText<
178
198
  return graphemes.join("");
179
199
  }
180
200
  }
201
+
202
+ function splitIntoChunks(text: string[]) {
203
+ const chunks: string[][] = [];
204
+ for (
205
+ let i = 0;
206
+ i < text.length;
207
+ i += TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE
208
+ ) {
209
+ chunks.push(text.slice(i, i + TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE));
210
+ }
211
+ return chunks;
212
+ }