cojson 0.13.16 → 0.13.18

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 (168) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/dist/PeerState.d.ts +3 -0
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +9 -0
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts.map +1 -1
  8. package/dist/SyncStateManager.js +2 -3
  9. package/dist/SyncStateManager.js.map +1 -1
  10. package/dist/coValue.d.ts +6 -4
  11. package/dist/coValue.d.ts.map +1 -1
  12. package/dist/coValue.js +5 -4
  13. package/dist/coValue.js.map +1 -1
  14. package/dist/coValueCore/coValueCore.d.ts +143 -0
  15. package/dist/coValueCore/coValueCore.d.ts.map +1 -0
  16. package/dist/{coValueCore.js → coValueCore/coValueCore.js} +314 -246
  17. package/dist/coValueCore/coValueCore.js.map +1 -0
  18. package/dist/coValueCore/verifiedState.d.ts +65 -0
  19. package/dist/coValueCore/verifiedState.d.ts.map +1 -0
  20. package/dist/coValueCore/verifiedState.js +210 -0
  21. package/dist/coValueCore/verifiedState.js.map +1 -0
  22. package/dist/coValues/account.d.ts +8 -10
  23. package/dist/coValues/account.d.ts.map +1 -1
  24. package/dist/coValues/account.js +12 -13
  25. package/dist/coValues/account.js.map +1 -1
  26. package/dist/coValues/coList.d.ts +10 -6
  27. package/dist/coValues/coList.d.ts.map +1 -1
  28. package/dist/coValues/coList.js +41 -15
  29. package/dist/coValues/coList.js.map +1 -1
  30. package/dist/coValues/coMap.d.ts +4 -3
  31. package/dist/coValues/coMap.d.ts.map +1 -1
  32. package/dist/coValues/coMap.js +5 -3
  33. package/dist/coValues/coMap.js.map +1 -1
  34. package/dist/coValues/coPlainText.d.ts +2 -2
  35. package/dist/coValues/coPlainText.d.ts.map +1 -1
  36. package/dist/coValues/coPlainText.js +5 -5
  37. package/dist/coValues/coPlainText.js.map +1 -1
  38. package/dist/coValues/coStream.d.ts +5 -4
  39. package/dist/coValues/coStream.d.ts.map +1 -1
  40. package/dist/coValues/coStream.js +5 -3
  41. package/dist/coValues/coStream.js.map +1 -1
  42. package/dist/coValues/group.d.ts +7 -2
  43. package/dist/coValues/group.d.ts.map +1 -1
  44. package/dist/coValues/group.js +29 -26
  45. package/dist/coValues/group.js.map +1 -1
  46. package/dist/coreToCoValue.d.ts +4 -3
  47. package/dist/coreToCoValue.d.ts.map +1 -1
  48. package/dist/coreToCoValue.js +10 -14
  49. package/dist/coreToCoValue.js.map +1 -1
  50. package/dist/exports.d.ts +6 -5
  51. package/dist/exports.d.ts.map +1 -1
  52. package/dist/exports.js +3 -4
  53. package/dist/exports.js.map +1 -1
  54. package/dist/localNode.d.ts +30 -24
  55. package/dist/localNode.d.ts.map +1 -1
  56. package/dist/localNode.js +153 -177
  57. package/dist/localNode.js.map +1 -1
  58. package/dist/permissions.d.ts +2 -1
  59. package/dist/permissions.d.ts.map +1 -1
  60. package/dist/permissions.js +15 -11
  61. package/dist/permissions.js.map +1 -1
  62. package/dist/priority.d.ts +1 -1
  63. package/dist/priority.d.ts.map +1 -1
  64. package/dist/sync.d.ts +2 -2
  65. package/dist/sync.d.ts.map +1 -1
  66. package/dist/sync.js +86 -55
  67. package/dist/sync.js.map +1 -1
  68. package/dist/tests/coList.test.js +133 -13
  69. package/dist/tests/coList.test.js.map +1 -1
  70. package/dist/tests/coMap.test.js +43 -14
  71. package/dist/tests/coMap.test.js.map +1 -1
  72. package/dist/tests/coPlainText.test.js +9 -10
  73. package/dist/tests/coPlainText.test.js.map +1 -1
  74. package/dist/tests/coStream.test.js +49 -18
  75. package/dist/tests/coStream.test.js.map +1 -1
  76. package/dist/tests/coValueCore.test.js +22 -28
  77. package/dist/tests/coValueCore.test.js.map +1 -1
  78. package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
  79. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
  80. package/dist/tests/coValueCoreLoadingState.test.js +227 -0
  81. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
  82. package/dist/tests/group.test.js +42 -43
  83. package/dist/tests/group.test.js.map +1 -1
  84. package/dist/tests/messagesTestUtils.d.ts +2 -2
  85. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  86. package/dist/tests/messagesTestUtils.js +1 -1
  87. package/dist/tests/messagesTestUtils.js.map +1 -1
  88. package/dist/tests/permissions.test.js +224 -292
  89. package/dist/tests/permissions.test.js.map +1 -1
  90. package/dist/tests/priority.test.js +13 -14
  91. package/dist/tests/priority.test.js.map +1 -1
  92. package/dist/tests/sync.auth.test.d.ts +2 -0
  93. package/dist/tests/sync.auth.test.d.ts.map +1 -0
  94. package/dist/tests/sync.auth.test.js +141 -0
  95. package/dist/tests/sync.auth.test.js.map +1 -0
  96. package/dist/tests/sync.load.test.js +60 -2
  97. package/dist/tests/sync.load.test.js.map +1 -1
  98. package/dist/tests/sync.mesh.test.js +70 -10
  99. package/dist/tests/sync.mesh.test.js.map +1 -1
  100. package/dist/tests/sync.peerReconciliation.test.js +19 -19
  101. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  102. package/dist/tests/sync.storage.test.js +20 -13
  103. package/dist/tests/sync.storage.test.js.map +1 -1
  104. package/dist/tests/sync.test.js +32 -39
  105. package/dist/tests/sync.test.js.map +1 -1
  106. package/dist/tests/sync.upload.test.js +126 -37
  107. package/dist/tests/sync.upload.test.js.map +1 -1
  108. package/dist/tests/testUtils.d.ts +24 -15
  109. package/dist/tests/testUtils.d.ts.map +1 -1
  110. package/dist/tests/testUtils.js +88 -61
  111. package/dist/tests/testUtils.js.map +1 -1
  112. package/dist/typeUtils/expectGroup.js +1 -1
  113. package/dist/typeUtils/expectGroup.js.map +1 -1
  114. package/package.json +1 -1
  115. package/src/PeerState.ts +11 -0
  116. package/src/SyncStateManager.ts +2 -3
  117. package/src/coValue.ts +14 -8
  118. package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +470 -413
  119. package/src/coValueCore/verifiedState.ts +376 -0
  120. package/src/coValues/account.ts +20 -25
  121. package/src/coValues/coList.ts +63 -29
  122. package/src/coValues/coMap.ts +13 -6
  123. package/src/coValues/coPlainText.ts +10 -8
  124. package/src/coValues/coStream.ts +12 -7
  125. package/src/coValues/group.ts +50 -28
  126. package/src/coreToCoValue.ts +14 -15
  127. package/src/exports.ts +9 -7
  128. package/src/localNode.ts +248 -283
  129. package/src/permissions.ts +18 -12
  130. package/src/priority.ts +1 -1
  131. package/src/sync.ts +96 -63
  132. package/src/tests/coList.test.ts +200 -12
  133. package/src/tests/coMap.test.ts +65 -14
  134. package/src/tests/coPlainText.test.ts +12 -9
  135. package/src/tests/coStream.test.ts +80 -17
  136. package/src/tests/coValueCore.test.ts +30 -27
  137. package/src/tests/coValueCoreLoadingState.test.ts +337 -0
  138. package/src/tests/group.test.ts +44 -68
  139. package/src/tests/messagesTestUtils.ts +3 -8
  140. package/src/tests/permissions.test.ts +283 -449
  141. package/src/tests/priority.test.ts +17 -13
  142. package/src/tests/sync.auth.test.ts +188 -0
  143. package/src/tests/sync.load.test.ts +79 -2
  144. package/src/tests/sync.mesh.test.ts +89 -9
  145. package/src/tests/sync.peerReconciliation.test.ts +25 -25
  146. package/src/tests/sync.storage.test.ts +20 -13
  147. package/src/tests/sync.test.ts +43 -43
  148. package/src/tests/sync.upload.test.ts +157 -37
  149. package/src/tests/testUtils.ts +120 -74
  150. package/src/typeUtils/expectGroup.ts +1 -1
  151. package/dist/CoValuesStore.d.ts +0 -14
  152. package/dist/CoValuesStore.d.ts.map +0 -1
  153. package/dist/CoValuesStore.js +0 -32
  154. package/dist/CoValuesStore.js.map +0 -1
  155. package/dist/coValueCore.d.ts +0 -141
  156. package/dist/coValueCore.d.ts.map +0 -1
  157. package/dist/coValueCore.js.map +0 -1
  158. package/dist/coValueState.d.ts +0 -34
  159. package/dist/coValueState.d.ts.map +0 -1
  160. package/dist/coValueState.js +0 -228
  161. package/dist/coValueState.js.map +0 -1
  162. package/dist/tests/coValueState.test.d.ts +0 -2
  163. package/dist/tests/coValueState.test.d.ts.map +0 -1
  164. package/dist/tests/coValueState.test.js +0 -344
  165. package/dist/tests/coValueState.test.js.map +0 -1
  166. package/src/CoValuesStore.ts +0 -41
  167. package/src/coValueState.ts +0 -300
  168. package/src/tests/coValueState.test.ts +0 -525
package/src/localNode.ts CHANGED
@@ -1,14 +1,19 @@
1
1
  import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
2
- import { CoValuesStore } from "./CoValuesStore.js";
3
2
  import { CoID } from "./coValue.js";
4
3
  import { RawCoValue } from "./coValue.js";
5
4
  import {
5
+ AvailableCoValueCore,
6
6
  CoValueCore,
7
+ idforHeader,
8
+ } from "./coValueCore/coValueCore.js";
9
+ import {
7
10
  CoValueHeader,
8
11
  CoValueUniqueness,
9
- } from "./coValueCore.js";
12
+ VerifiedState,
13
+ } from "./coValueCore/verifiedState.js";
10
14
  import {
11
15
  AccountMeta,
16
+ ControlledAccount,
12
17
  ControlledAccountOrAgent,
13
18
  ControlledAgent,
14
19
  InvalidAccountAgentIDError,
@@ -16,9 +21,9 @@ import {
16
21
  RawAccount,
17
22
  RawAccountID,
18
23
  RawAccountMigration,
19
- RawControlledAccount,
20
24
  RawProfile,
21
25
  accountHeaderForInitialAgentSecret,
26
+ expectAccount,
22
27
  } from "./coValues/account.js";
23
28
  import {
24
29
  InviteSecret,
@@ -29,6 +34,7 @@ import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
29
34
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
30
35
  import { logger } from "./logger.js";
31
36
  import { Peer, PeerID, SyncManager } from "./sync.js";
37
+ import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
32
38
  import { expectGroup } from "./typeUtils/expectGroup.js";
33
39
 
34
40
  /** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
@@ -46,11 +52,12 @@ export class LocalNode {
46
52
  /** @internal */
47
53
  crypto: CryptoProvider;
48
54
  /** @internal */
49
- coValuesStore = new CoValuesStore();
50
- /** @category 3. Low-level */
51
- account: ControlledAccountOrAgent;
55
+ private readonly coValues = new Map<RawCoID, CoValueCore>();
56
+
52
57
  /** @category 3. Low-level */
53
- currentSessionID: SessionID;
58
+ readonly currentSessionID: SessionID;
59
+ readonly agentSecret: AgentSecret;
60
+
54
61
  /** @category 3. Low-level */
55
62
  syncManager = new SyncManager(this);
56
63
 
@@ -58,17 +65,129 @@ export class LocalNode {
58
65
 
59
66
  /** @category 3. Low-level */
60
67
  constructor(
61
- account: ControlledAccountOrAgent,
68
+ agentSecret: AgentSecret,
62
69
  currentSessionID: SessionID,
63
70
  crypto: CryptoProvider,
64
71
  ) {
65
- this.account = account;
72
+ this.agentSecret = agentSecret;
66
73
  this.currentSessionID = currentSessionID;
67
74
  this.crypto = crypto;
68
75
  }
69
76
 
77
+ getCoValue(id: RawCoID) {
78
+ let entry = this.coValues.get(id);
79
+
80
+ if (!entry) {
81
+ entry = CoValueCore.fromID(id, this);
82
+ this.coValues.set(id, entry);
83
+ }
84
+
85
+ return entry;
86
+ }
87
+
88
+ allCoValues() {
89
+ return this.coValues.values();
90
+ }
91
+
92
+ private putCoValue(
93
+ id: RawCoID,
94
+ verified: VerifiedState,
95
+ { forceOverwrite = false }: { forceOverwrite?: boolean } = {},
96
+ ): AvailableCoValueCore {
97
+ const entry = this.getCoValue(id);
98
+ entry.internalMarkMagicallyAvailable(verified, { forceOverwrite });
99
+ return entry as AvailableCoValueCore;
100
+ }
101
+
102
+ internalDeleteCoValue(id: RawCoID) {
103
+ this.coValues.delete(id);
104
+ }
105
+
106
+ getCurrentAgent(): ControlledAccountOrAgent {
107
+ const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
108
+ if (isAgentID(accountOrAgent)) {
109
+ return new ControlledAgent(this.agentSecret, this.crypto);
110
+ }
111
+ return new ControlledAccount(
112
+ expectAccount(
113
+ this.expectCoValueLoaded(accountOrAgent).getCurrentContent(),
114
+ ),
115
+ this.agentSecret,
116
+ );
117
+ }
118
+
119
+ expectCurrentAccountID(reason: string): RawAccountID {
120
+ const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
121
+ if (isAgentID(accountOrAgent)) {
122
+ throw new Error(
123
+ "Current account is an agent, but expected an account: " + reason,
124
+ );
125
+ }
126
+ return accountOrAgent;
127
+ }
128
+
129
+ expectCurrentAccount(reason: string): RawAccount {
130
+ const accountID = this.expectCurrentAccountID(reason);
131
+ return expectAccount(
132
+ this.expectCoValueLoaded(accountID).getCurrentContent(),
133
+ );
134
+ }
135
+
136
+ static internalCreateAccount(opts: {
137
+ crypto: CryptoProvider;
138
+ initialAgentSecret?: AgentSecret;
139
+ peersToLoadFrom?: Peer[];
140
+ }): RawAccount {
141
+ const {
142
+ crypto,
143
+ initialAgentSecret = crypto.newRandomAgentSecret(),
144
+ peersToLoadFrom = [],
145
+ } = opts;
146
+ const accountHeader = accountHeaderForInitialAgentSecret(
147
+ initialAgentSecret,
148
+ crypto,
149
+ );
150
+ const accountID = idforHeader(accountHeader, crypto);
151
+
152
+ const node = new LocalNode(
153
+ initialAgentSecret,
154
+ crypto.newRandomSessionID(accountID as RawAccountID),
155
+ crypto,
156
+ );
157
+
158
+ for (const peer of peersToLoadFrom) {
159
+ node.syncManager.addPeer(peer);
160
+ }
161
+
162
+ const accountAgentID = crypto.getAgentID(initialAgentSecret);
163
+
164
+ const rawAccount = expectGroup(
165
+ node.createCoValue(accountHeader).getCurrentContent(),
166
+ );
167
+
168
+ rawAccount.set(accountAgentID, "admin", "trusting");
169
+
170
+ const readKey = crypto.newRandomKeySecret();
171
+
172
+ const sealed = crypto.seal({
173
+ message: readKey.secret,
174
+ from: crypto.getAgentSealerSecret(initialAgentSecret),
175
+ to: crypto.getAgentSealerID(accountAgentID),
176
+ nOnceMaterial: {
177
+ in: rawAccount.id,
178
+ tx: rawAccount.core.nextTransactionID(),
179
+ },
180
+ });
181
+
182
+ rawAccount.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
183
+
184
+ rawAccount.set("readKey", readKey.id, "trusting");
185
+
186
+ return node.expectCurrentAccount("after creation");
187
+ }
188
+
70
189
  /** @category 2. Node Creation */
71
- static async withNewlyCreatedAccount<Meta extends AccountMeta = AccountMeta>({
190
+ static async withNewlyCreatedAccount({
72
191
  creationProps,
73
192
  peersToLoadFrom,
74
193
  migration,
@@ -77,7 +196,7 @@ export class LocalNode {
77
196
  }: {
78
197
  creationProps: { name: string };
79
198
  peersToLoadFrom?: Peer[];
80
- migration?: RawAccountMigration<Meta>;
199
+ migration?: RawAccountMigration<AccountMeta>;
81
200
  crypto: CryptoProvider;
82
201
  initialAgentSecret?: AgentSecret;
83
202
  }): Promise<{
@@ -86,81 +205,38 @@ export class LocalNode {
86
205
  accountSecret: AgentSecret;
87
206
  sessionID: SessionID;
88
207
  }> {
89
- const throwawayAgent = crypto.newRandomAgentSecret();
90
- const setupNode = new LocalNode(
91
- new ControlledAgent(throwawayAgent, crypto),
92
- crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
208
+ const account = LocalNode.internalCreateAccount({
93
209
  crypto,
94
- );
95
-
96
- const account = setupNode.createAccount(initialAgentSecret);
97
-
98
- const nodeWithAccount = account.core.node.testWithDifferentAccount(
99
- account,
100
- crypto.newRandomSessionID(account.id),
101
- );
102
-
103
- const accountOnNodeWithAccount =
104
- nodeWithAccount.account as RawControlledAccount<Meta>;
105
-
106
- if (peersToLoadFrom) {
107
- for (const peer of peersToLoadFrom) {
108
- nodeWithAccount.syncManager.addPeer(peer);
109
- }
110
- }
210
+ initialAgentSecret,
211
+ peersToLoadFrom,
212
+ });
213
+ const node = account.core.node;
111
214
 
112
215
  if (migration) {
113
- await migration(accountOnNodeWithAccount, nodeWithAccount, creationProps);
216
+ await migration(account, node, creationProps);
114
217
  } else {
115
- const profileGroup = accountOnNodeWithAccount.createGroup();
218
+ const profileGroup = node.createGroup();
116
219
  profileGroup.addMember("everyone", "reader");
117
220
  const profile = profileGroup.createMap<Profile>({
118
221
  name: creationProps.name,
119
222
  });
120
- accountOnNodeWithAccount.set("profile", profile.id, "trusting");
223
+ account.set("profile", profile.id, "trusting");
121
224
  }
122
225
 
123
- const controlledAccount = new RawControlledAccount(
124
- accountOnNodeWithAccount.core,
125
- accountOnNodeWithAccount.agentSecret,
126
- );
127
-
128
- nodeWithAccount.account = controlledAccount;
129
- nodeWithAccount.coValuesStore.internalMarkMagicallyAvailable(
130
- controlledAccount.id,
131
- controlledAccount.core,
132
- );
133
- controlledAccount.core._cachedContent = undefined;
134
-
135
- if (!controlledAccount.get("profile")) {
226
+ if (!account.get("profile")) {
136
227
  throw new Error("Must set account profile in initial migration");
137
228
  }
138
229
 
139
- // we shouldn't need this, but it fixes account data not syncing for new accounts
140
- function syncAllCoValuesAfterCreateAccount() {
141
- for (const coValueEntry of nodeWithAccount.coValuesStore.getValues()) {
142
- if (coValueEntry.isAvailable()) {
143
- void nodeWithAccount.syncManager.requestCoValueSync(
144
- coValueEntry.core,
145
- );
146
- }
147
- }
148
- }
149
-
150
- syncAllCoValuesAfterCreateAccount();
151
-
152
- setTimeout(syncAllCoValuesAfterCreateAccount, 500);
153
-
154
230
  return {
155
- node: nodeWithAccount,
156
- accountID: accountOnNodeWithAccount.id,
157
- accountSecret: accountOnNodeWithAccount.agentSecret,
158
- sessionID: nodeWithAccount.currentSessionID,
231
+ node,
232
+ accountID: account.id,
233
+ accountSecret: initialAgentSecret,
234
+ sessionID: node.currentSessionID,
159
235
  };
160
236
  }
161
237
 
162
238
  /** @category 2. Node Creation */
163
- static async withLoadedAccount<Meta extends AccountMeta = AccountMeta>({
239
+ static async withLoadedAccount({
164
240
  accountID,
165
241
  accountSecret,
166
242
  sessionID,
@@ -173,47 +249,25 @@ export class LocalNode {
173
249
  sessionID: SessionID | undefined;
174
250
  peersToLoadFrom: Peer[];
175
251
  crypto: CryptoProvider;
176
- migration?: RawAccountMigration<Meta>;
252
+ migration?: RawAccountMigration<AccountMeta>;
177
253
  }): Promise<LocalNode> {
178
254
  try {
179
- const loadingNode = new LocalNode(
180
- new ControlledAgent(accountSecret, crypto),
181
- crypto.newRandomSessionID(accountID),
255
+ const node = new LocalNode(
256
+ accountSecret,
257
+ sessionID || crypto.newRandomSessionID(accountID),
182
258
  crypto,
183
259
  );
184
260
 
185
261
  for (const peer of peersToLoadFrom) {
186
- loadingNode.syncManager.addPeer(peer);
262
+ node.syncManager.addPeer(peer);
187
263
  }
188
264
 
189
- const accountPromise = loadingNode.load(accountID);
190
-
191
- const account = await accountPromise;
265
+ const account = await node.load(accountID);
192
266
 
193
267
  if (account === "unavailable") {
194
268
  throw new Error("Account unavailable from all peers");
195
269
  }
196
270
 
197
- const controlledAccount = new RawControlledAccount(
198
- account.core,
199
- accountSecret,
200
- );
201
-
202
- // since this is all synchronous, we can just swap out nodes for the SyncManager
203
- const node = loadingNode.testWithDifferentAccount(
204
- controlledAccount,
205
- sessionID || crypto.newRandomSessionID(accountID),
206
- );
207
- node.syncManager = loadingNode.syncManager;
208
- node.syncManager.local = node;
209
-
210
- controlledAccount.core.node = node;
211
- node.coValuesStore.internalMarkMagicallyAvailable(
212
- accountID,
213
- controlledAccount.core,
214
- );
215
- controlledAccount.core._cachedContent = undefined;
216
-
217
271
  const profileID = account.get("profile");
218
272
  if (!profileID) {
219
273
  throw new Error("Account has no profile");
@@ -225,11 +279,7 @@ export class LocalNode {
225
279
  }
226
280
 
227
281
  if (migration) {
228
- await migration(controlledAccount as RawControlledAccount<Meta>, node);
229
- node.account = new RawControlledAccount(
230
- controlledAccount.core,
231
- controlledAccount.agentSecret,
232
- );
282
+ await migration(account, node);
233
283
  }
234
284
 
235
285
  return node;
@@ -240,15 +290,19 @@ export class LocalNode {
240
290
  }
241
291
 
242
292
  /** @internal */
243
- createCoValue(header: CoValueHeader): CoValueCore {
293
+ createCoValue(header: CoValueHeader): AvailableCoValueCore {
244
294
  if (this.crashed) {
245
295
  throw new Error("Trying to create CoValue after node has crashed", {
246
296
  cause: this.crashed,
247
297
  });
248
298
  }
249
299
 
250
- const coValue = new CoValueCore(header, this);
251
- this.coValuesStore.internalMarkMagicallyAvailable(coValue.id, coValue);
300
+ const id = idforHeader(header, this.crypto);
301
+
302
+ const coValue = this.putCoValue(
303
+ id,
304
+ new VerifiedState(id, this.crypto, header, new Map()),
305
+ );
252
306
 
253
307
  void this.syncManager.requestCoValueSync(coValue);
254
308
 
@@ -259,36 +313,46 @@ export class LocalNode {
259
313
  async loadCoValueCore(
260
314
  id: RawCoID,
261
315
  skipLoadingFromPeer?: PeerID,
262
- ): Promise<CoValueCore | "unavailable"> {
316
+ ): Promise<CoValueCore> {
263
317
  if (this.crashed) {
264
318
  throw new Error("Trying to load CoValue after node has crashed", {
265
319
  cause: this.crashed,
266
320
  });
267
321
  }
268
322
 
269
- const entry = this.coValuesStore.get(id);
323
+ let retries = 0;
270
324
 
271
- if (
272
- entry.highLevelState === "unknown" ||
273
- entry.highLevelState === "unavailable"
274
- ) {
275
- const peers =
276
- this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
325
+ while (true) {
326
+ const coValue = this.getCoValue(id);
277
327
 
278
- if (peers.length === 0) {
279
- return "unavailable";
280
- }
328
+ if (
329
+ coValue.loadingState === "unknown" ||
330
+ coValue.loadingState === "unavailable"
331
+ ) {
332
+ const peers =
333
+ this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
281
334
 
282
- await entry.loadFromPeers(peers).catch((e) => {
283
- logger.error("Error loading from peers", {
284
- id,
285
- err: e,
335
+ if (peers.length === 0) {
336
+ return coValue;
337
+ }
338
+
339
+ coValue.loadFromPeers(peers).catch((e) => {
340
+ logger.error("Error loading from peers", {
341
+ id,
342
+ err: e,
343
+ });
286
344
  });
287
- });
288
- }
345
+ }
346
+
347
+ const result = await coValue.waitForAvailableOrUnavailable();
348
+ if (result.isAvailable() || retries >= 1) {
349
+ return result;
350
+ }
351
+
352
+ await new Promise((resolve) => setTimeout(resolve, 300));
289
353
 
290
- // TODO: What if the loading fails because in the previous loadCoValueCore call the Peer with the covalue was skipped?
291
- return entry.getCoValue();
354
+ retries++;
355
+ }
292
356
  }
293
357
 
294
358
  /**
@@ -309,7 +373,7 @@ export class LocalNode {
309
373
 
310
374
  const core = await this.loadCoValueCore(id);
311
375
 
312
- if (core === "unavailable") {
376
+ if (!core.isAvailable()) {
313
377
  return "unavailable";
314
378
  }
315
379
 
@@ -317,10 +381,10 @@ export class LocalNode {
317
381
  }
318
382
 
319
383
  getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
320
- const entry = this.coValuesStore.get(id);
384
+ const coValue = this.getCoValue(id);
321
385
 
322
- if (entry.isAvailable()) {
323
- return entry.core.getCurrentContent() as T;
386
+ if (coValue.isAvailable()) {
387
+ return coValue.getCurrentContent() as T;
324
388
  }
325
389
 
326
390
  return undefined;
@@ -358,7 +422,6 @@ export class LocalNode {
358
422
  };
359
423
  }
360
424
 
361
- /** @deprecated Use Account.acceptInvite instead */
362
425
  async acceptInvite<T extends RawCoValue>(
363
426
  groupOrOwnedValueID: CoID<T>,
364
427
  inviteSecret: InviteSecret,
@@ -371,12 +434,16 @@ export class LocalNode {
371
434
  );
372
435
  }
373
436
 
374
- if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
437
+ if (
438
+ groupOrOwnedValue.core.verified.header.ruleset.type === "ownedByGroup"
439
+ ) {
375
440
  return this.acceptInvite(
376
- groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
441
+ groupOrOwnedValue.core.verified.header.ruleset.group as CoID<RawGroup>,
377
442
  inviteSecret,
378
443
  );
379
- } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
444
+ } else if (
445
+ groupOrOwnedValue.core.verified.header.ruleset.type !== "group"
446
+ ) {
380
447
  throw new Error("Can only accept invites to groups");
381
448
  }
382
449
 
@@ -404,7 +471,8 @@ export class LocalNode {
404
471
  throw new Error("No invite found");
405
472
  }
406
473
 
407
- const existingRole = group.get(this.account.id);
474
+ const account = this.getCurrentAgent();
475
+ const existingRole = group.get(account.id);
408
476
 
409
477
  if (
410
478
  existingRole === "admin" ||
@@ -418,16 +486,13 @@ export class LocalNode {
418
486
  }
419
487
 
420
488
  const groupAsInvite = expectGroup(
421
- group.core
422
- .testWithDifferentAccount(
423
- new ControlledAgent(inviteAgentSecret, this.crypto),
424
- this.crypto.newRandomSessionID(inviteAgentID),
425
- )
426
- .getCurrentContent(),
489
+ group.core.contentInClonedNodeWithDifferentAccount(
490
+ new ControlledAgent(inviteAgentSecret, this.crypto),
491
+ ),
427
492
  );
428
493
 
429
494
  groupAsInvite.addMemberInternal(
430
- this.account,
495
+ account,
431
496
  inviteRole === "adminInvite"
432
497
  ? "admin"
433
498
  : inviteRole === "writerInvite"
@@ -437,24 +502,25 @@ export class LocalNode {
437
502
  : "reader",
438
503
  );
439
504
 
440
- group.core._sessionLogs = groupAsInvite.core.sessionLogs;
441
- group.core._cachedContent = undefined;
505
+ group.core.internalShamefullyCloneVerifiedStateFrom(
506
+ groupAsInvite.core.verified,
507
+ { forceOverwrite: true },
508
+ );
509
+ group.core.internalShamefullyResetCachedContent();
442
510
 
443
- for (const groupListener of group.core.listeners) {
444
- groupListener(group.core.getCurrentContent());
445
- }
511
+ group.core.notifyUpdate("immediate");
446
512
  }
447
513
 
448
514
  /** @internal */
449
- expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
450
- const entry = this.coValuesStore.get(id);
515
+ expectCoValueLoaded(id: RawCoID, expectation?: string): AvailableCoValueCore {
516
+ const coValue = this.getCoValue(id);
451
517
 
452
- if (!entry.isAvailable()) {
518
+ if (!coValue.isAvailable()) {
453
519
  throw new Error(
454
- `${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(entry)}`,
520
+ `${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(coValue)}`,
455
521
  );
456
522
  }
457
- return entry.core;
523
+ return coValue;
458
524
  }
459
525
 
460
526
  /** @internal */
@@ -472,49 +538,6 @@ export class LocalNode {
472
538
  ).getCurrentContent() as RawProfile;
473
539
  }
474
540
 
475
- /** @internal */
476
- createAccount(
477
- agentSecret = this.crypto.newRandomAgentSecret(),
478
- ): RawControlledAccount {
479
- const accountAgentID = this.crypto.getAgentID(agentSecret);
480
- const account = expectGroup(
481
- this.createCoValue(
482
- accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
483
- )
484
- .testWithDifferentAccount(
485
- new ControlledAgent(agentSecret, this.crypto),
486
- this.crypto.newRandomSessionID(accountAgentID),
487
- )
488
- .getCurrentContent(),
489
- );
490
-
491
- account.set(accountAgentID, "admin", "trusting");
492
-
493
- const readKey = this.crypto.newRandomKeySecret();
494
-
495
- const sealed = this.crypto.seal({
496
- message: readKey.secret,
497
- from: this.crypto.getAgentSealerSecret(agentSecret),
498
- to: this.crypto.getAgentSealerID(accountAgentID),
499
- nOnceMaterial: {
500
- in: account.id,
501
- tx: account.core.nextTransactionID(),
502
- },
503
- });
504
-
505
- account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
506
-
507
- account.set("readKey", readKey.id, "trusting");
508
-
509
- const accountOnThisNode = this.expectCoValueLoaded(account.id);
510
-
511
- accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
512
-
513
- accountOnThisNode._cachedContent = undefined;
514
-
515
- return new RawControlledAccount(accountOnThisNode, agentSecret);
516
- }
517
-
518
541
  /** @internal */
519
542
  resolveAccountAgent(
520
543
  id: RawAccountID | AgentID,
@@ -524,7 +547,7 @@ export class LocalNode {
524
547
  return ok(id);
525
548
  }
526
549
 
527
- let coValue: CoValueCore;
550
+ let coValue: AvailableCoValueCore;
528
551
 
529
552
  try {
530
553
  coValue = this.expectCoValueLoaded(id, expectation);
@@ -538,11 +561,11 @@ export class LocalNode {
538
561
  }
539
562
 
540
563
  if (
541
- coValue.header.type !== "comap" ||
542
- coValue.header.ruleset.type !== "group" ||
543
- !coValue.header.meta ||
544
- !("type" in coValue.header.meta) ||
545
- coValue.header.meta.type !== "account"
564
+ coValue.verified.header.type !== "comap" ||
565
+ coValue.verified.header.ruleset.type !== "group" ||
566
+ !coValue.verified.header.meta ||
567
+ !("type" in coValue.verified.header.meta) ||
568
+ coValue.verified.header.meta.type !== "account"
546
569
  ) {
547
570
  return err({
548
571
  type: "UnexpectedlyNotAccount",
@@ -554,75 +577,30 @@ export class LocalNode {
554
577
  return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
555
578
  }
556
579
 
557
- resolveAccountAgentAsync(
558
- id: RawAccountID | AgentID,
559
- expectation?: string,
560
- ): ResultAsync<AgentID, ResolveAccountAgentError> {
561
- if (isAgentID(id)) {
562
- return okAsync(id);
563
- }
564
-
565
- return ResultAsync.fromPromise(
566
- this.loadCoValueCore(id),
567
- (e) =>
568
- ({
569
- type: "ErrorLoadingCoValueCore",
570
- expectation,
571
- id,
572
- error: e,
573
- }) satisfies LoadCoValueCoreError,
574
- ).andThen((coValue) => {
575
- if (coValue === "unavailable") {
576
- return err({
577
- type: "AccountUnavailableFromAllPeers" as const,
578
- expectation,
579
- id,
580
- } satisfies AccountUnavailableFromAllPeersError);
581
- }
582
-
583
- if (
584
- coValue.header.type !== "comap" ||
585
- coValue.header.ruleset.type !== "group" ||
586
- !coValue.header.meta ||
587
- !("type" in coValue.header.meta) ||
588
- coValue.header.meta.type !== "account"
589
- ) {
590
- return err({
591
- type: "UnexpectedlyNotAccount" as const,
592
- expectation,
593
- id,
594
- } satisfies UnexpectedlyNotAccountError);
595
- }
596
-
597
- return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
598
- });
599
- }
600
-
601
- /**
602
- * @deprecated use Account.createGroup() instead
603
- */
604
580
  createGroup(
605
581
  uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
606
582
  ): RawGroup {
583
+ const account = this.getCurrentAgent();
584
+
607
585
  const groupCoValue = this.createCoValue({
608
586
  type: "comap",
609
- ruleset: { type: "group", initialAdmin: this.account.id },
587
+ ruleset: { type: "group", initialAdmin: account.id },
610
588
  meta: null,
611
589
  ...uniqueness,
612
590
  });
613
591
 
614
592
  const group = expectGroup(groupCoValue.getCurrentContent());
615
593
 
616
- group.set(this.account.id, "admin", "trusting");
594
+ group.set(account.id, "admin", "trusting");
617
595
 
618
596
  const readKey = this.crypto.newRandomKeySecret();
619
597
 
620
598
  group.set(
621
- `${readKey.id}_for_${this.account.id}`,
599
+ `${readKey.id}_for_${account.id}`,
622
600
  this.crypto.seal({
623
601
  message: readKey.secret,
624
- from: this.account.currentSealerSecret(),
625
- to: this.account.currentSealerID(),
602
+ from: account.currentSealerSecret(),
603
+ to: account.currentSealerID(),
626
604
  nOnceMaterial: {
627
605
  in: groupCoValue.id,
628
606
  tx: groupCoValue.nextTransactionID(),
@@ -637,24 +615,34 @@ export class LocalNode {
637
615
  }
638
616
 
639
617
  /** @internal */
640
- testWithDifferentAccount(
641
- account: ControlledAccountOrAgent,
642
- currentSessionID: SessionID,
618
+ cloneWithDifferentAccount(
619
+ controlledAccountOrAgent: ControlledAccountOrAgent,
643
620
  ): LocalNode {
644
- const newNode = new LocalNode(account, currentSessionID, this.crypto);
621
+ const newNode = new LocalNode(
622
+ controlledAccountOrAgent.agentSecret,
623
+ this.crypto.newRandomSessionID(controlledAccountOrAgent.id),
624
+ this.crypto,
625
+ );
626
+
627
+ newNode.cloneVerifiedStateFrom(this);
628
+
629
+ return newNode;
630
+ }
645
631
 
646
- const coValuesToCopy = Array.from(this.coValuesStore.getEntries());
632
+ /** @internal */
633
+ cloneVerifiedStateFrom(otherNode: LocalNode) {
634
+ const coValuesToCopy = Array.from(otherNode.coValues.entries());
647
635
 
648
636
  while (coValuesToCopy.length > 0) {
649
- const [coValueID, entry] = coValuesToCopy[coValuesToCopy.length - 1]!;
637
+ const [coValueID, coValue] = coValuesToCopy[coValuesToCopy.length - 1]!;
650
638
 
651
- if (!entry.isAvailable()) {
639
+ if (!coValue.isAvailable()) {
652
640
  coValuesToCopy.pop();
653
641
  continue;
654
642
  } else {
655
- const allDepsCopied = entry.core
643
+ const allDepsCopied = coValue
656
644
  .getDependedOnCoValues()
657
- .every((dep) => newNode.coValuesStore.get(dep).isAvailable());
645
+ .every((dep) => this.coValues.get(dep)?.isAvailable());
658
646
 
659
647
  if (!allDepsCopied) {
660
648
  // move to end of queue
@@ -662,34 +650,11 @@ export class LocalNode {
662
650
  continue;
663
651
  }
664
652
 
665
- const newCoValue = new CoValueCore(
666
- entry.core.header,
667
- newNode,
668
- new Map(entry.core.sessionLogs),
669
- );
670
-
671
- newNode.coValuesStore.internalMarkMagicallyAvailable(
672
- coValueID,
673
- newCoValue,
674
- );
653
+ this.putCoValue(coValueID, coValue.verified);
675
654
 
676
655
  coValuesToCopy.pop();
677
656
  }
678
657
  }
679
-
680
- if (account instanceof RawControlledAccount) {
681
- // To make sure that when we edit the account, we're modifying the correct sessions
682
- const accountInNode = new RawControlledAccount(
683
- newNode.expectCoValueLoaded(account.id),
684
- account.agentSecret,
685
- );
686
- if (accountInNode.core.node !== newNode) {
687
- throw new Error("Account's node is not the new node");
688
- }
689
- newNode.account = accountInNode;
690
- }
691
-
692
- return newNode;
693
658
  }
694
659
 
695
660
  gracefulShutdown() {