cojson 0.13.17 → 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 (166) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +9 -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 +4 -4
  11. package/dist/coValue.d.ts.map +1 -1
  12. package/dist/coValue.js +4 -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 +3 -3
  27. package/dist/coValues/coList.d.ts.map +1 -1
  28. package/dist/coValues/coList.js +6 -3
  29. package/dist/coValues/coList.js.map +1 -1
  30. package/dist/coValues/coMap.d.ts +3 -3
  31. package/dist/coValues/coMap.d.ts.map +1 -1
  32. package/dist/coValues/coMap.js +3 -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 +4 -4
  37. package/dist/coValues/coPlainText.js.map +1 -1
  38. package/dist/coValues/coStream.d.ts +3 -3
  39. package/dist/coValues/coStream.d.ts.map +1 -1
  40. package/dist/coValues/coStream.js +3 -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 +2 -2
  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 +139 -170
  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 +19 -16
  69. package/dist/tests/coList.test.js.map +1 -1
  70. package/dist/tests/coMap.test.js +12 -13
  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 +22 -17
  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/{coValueState.test.js → coValueCoreLoadingState.test.js} +62 -46
  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 +4 -4
  97. package/dist/tests/sync.load.test.js.map +1 -1
  98. package/dist/tests/sync.mesh.test.js +25 -12
  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 +11 -8
  118. package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +469 -413
  119. package/src/coValueCore/verifiedState.ts +376 -0
  120. package/src/coValues/account.ts +20 -25
  121. package/src/coValues/coList.ts +12 -6
  122. package/src/coValues/coMap.ts +9 -6
  123. package/src/coValues/coPlainText.ts +9 -6
  124. package/src/coValues/coStream.ts +9 -6
  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 +227 -273
  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 +21 -15
  133. package/src/tests/coMap.test.ts +12 -13
  134. package/src/tests/coPlainText.test.ts +12 -9
  135. package/src/tests/coStream.test.ts +25 -16
  136. package/src/tests/coValueCore.test.ts +30 -27
  137. package/src/tests/{coValueState.test.ts → coValueCoreLoadingState.test.ts} +67 -57
  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 +4 -4
  144. package/src/tests/sync.mesh.test.ts +25 -12
  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 -142
  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 -190
  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.map +0 -1
  165. package/src/CoValuesStore.ts +0 -41
  166. package/src/coValueState.ts +0 -245
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,7 +313,7 @@ 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,
@@ -269,20 +323,20 @@ export class LocalNode {
269
323
  let retries = 0;
270
324
 
271
325
  while (true) {
272
- const entry = this.coValuesStore.get(id);
326
+ const coValue = this.getCoValue(id);
273
327
 
274
328
  if (
275
- entry.highLevelState === "unknown" ||
276
- entry.highLevelState === "unavailable"
329
+ coValue.loadingState === "unknown" ||
330
+ coValue.loadingState === "unavailable"
277
331
  ) {
278
332
  const peers =
279
333
  this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
280
334
 
281
335
  if (peers.length === 0) {
282
- return "unavailable";
336
+ return coValue;
283
337
  }
284
338
 
285
- entry.loadFromPeers(peers).catch((e) => {
339
+ coValue.loadFromPeers(peers).catch((e) => {
286
340
  logger.error("Error loading from peers", {
287
341
  id,
288
342
  err: e,
@@ -290,9 +344,8 @@ export class LocalNode {
290
344
  });
291
345
  }
292
346
 
293
- const result = await entry.getCoValue();
294
-
295
- if (result !== "unavailable" || retries >= 1) {
347
+ const result = await coValue.waitForAvailableOrUnavailable();
348
+ if (result.isAvailable() || retries >= 1) {
296
349
  return result;
297
350
  }
298
351
 
@@ -320,7 +373,7 @@ export class LocalNode {
320
373
 
321
374
  const core = await this.loadCoValueCore(id);
322
375
 
323
- if (core === "unavailable") {
376
+ if (!core.isAvailable()) {
324
377
  return "unavailable";
325
378
  }
326
379
 
@@ -328,10 +381,10 @@ export class LocalNode {
328
381
  }
329
382
 
330
383
  getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
331
- const entry = this.coValuesStore.get(id);
384
+ const coValue = this.getCoValue(id);
332
385
 
333
- if (entry.isAvailable()) {
334
- return entry.core.getCurrentContent() as T;
386
+ if (coValue.isAvailable()) {
387
+ return coValue.getCurrentContent() as T;
335
388
  }
336
389
 
337
390
  return undefined;
@@ -369,7 +422,6 @@ export class LocalNode {
369
422
  };
370
423
  }
371
424
 
372
- /** @deprecated Use Account.acceptInvite instead */
373
425
  async acceptInvite<T extends RawCoValue>(
374
426
  groupOrOwnedValueID: CoID<T>,
375
427
  inviteSecret: InviteSecret,
@@ -382,12 +434,16 @@ export class LocalNode {
382
434
  );
383
435
  }
384
436
 
385
- if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
437
+ if (
438
+ groupOrOwnedValue.core.verified.header.ruleset.type === "ownedByGroup"
439
+ ) {
386
440
  return this.acceptInvite(
387
- groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
441
+ groupOrOwnedValue.core.verified.header.ruleset.group as CoID<RawGroup>,
388
442
  inviteSecret,
389
443
  );
390
- } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
444
+ } else if (
445
+ groupOrOwnedValue.core.verified.header.ruleset.type !== "group"
446
+ ) {
391
447
  throw new Error("Can only accept invites to groups");
392
448
  }
393
449
 
@@ -415,7 +471,8 @@ export class LocalNode {
415
471
  throw new Error("No invite found");
416
472
  }
417
473
 
418
- const existingRole = group.get(this.account.id);
474
+ const account = this.getCurrentAgent();
475
+ const existingRole = group.get(account.id);
419
476
 
420
477
  if (
421
478
  existingRole === "admin" ||
@@ -429,16 +486,13 @@ export class LocalNode {
429
486
  }
430
487
 
431
488
  const groupAsInvite = expectGroup(
432
- group.core
433
- .testWithDifferentAccount(
434
- new ControlledAgent(inviteAgentSecret, this.crypto),
435
- this.crypto.newRandomSessionID(inviteAgentID),
436
- )
437
- .getCurrentContent(),
489
+ group.core.contentInClonedNodeWithDifferentAccount(
490
+ new ControlledAgent(inviteAgentSecret, this.crypto),
491
+ ),
438
492
  );
439
493
 
440
494
  groupAsInvite.addMemberInternal(
441
- this.account,
495
+ account,
442
496
  inviteRole === "adminInvite"
443
497
  ? "admin"
444
498
  : inviteRole === "writerInvite"
@@ -448,24 +502,25 @@ export class LocalNode {
448
502
  : "reader",
449
503
  );
450
504
 
451
- group.core._sessionLogs = groupAsInvite.core.sessionLogs;
452
- group.core._cachedContent = undefined;
505
+ group.core.internalShamefullyCloneVerifiedStateFrom(
506
+ groupAsInvite.core.verified,
507
+ { forceOverwrite: true },
508
+ );
509
+ group.core.internalShamefullyResetCachedContent();
453
510
 
454
- for (const groupListener of group.core.listeners) {
455
- groupListener(group.core.getCurrentContent());
456
- }
511
+ group.core.notifyUpdate("immediate");
457
512
  }
458
513
 
459
514
  /** @internal */
460
- expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
461
- const entry = this.coValuesStore.get(id);
515
+ expectCoValueLoaded(id: RawCoID, expectation?: string): AvailableCoValueCore {
516
+ const coValue = this.getCoValue(id);
462
517
 
463
- if (!entry.isAvailable()) {
518
+ if (!coValue.isAvailable()) {
464
519
  throw new Error(
465
- `${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)}`,
466
521
  );
467
522
  }
468
- return entry.core;
523
+ return coValue;
469
524
  }
470
525
 
471
526
  /** @internal */
@@ -483,49 +538,6 @@ export class LocalNode {
483
538
  ).getCurrentContent() as RawProfile;
484
539
  }
485
540
 
486
- /** @internal */
487
- createAccount(
488
- agentSecret = this.crypto.newRandomAgentSecret(),
489
- ): RawControlledAccount {
490
- const accountAgentID = this.crypto.getAgentID(agentSecret);
491
- const account = expectGroup(
492
- this.createCoValue(
493
- accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
494
- )
495
- .testWithDifferentAccount(
496
- new ControlledAgent(agentSecret, this.crypto),
497
- this.crypto.newRandomSessionID(accountAgentID),
498
- )
499
- .getCurrentContent(),
500
- );
501
-
502
- account.set(accountAgentID, "admin", "trusting");
503
-
504
- const readKey = this.crypto.newRandomKeySecret();
505
-
506
- const sealed = this.crypto.seal({
507
- message: readKey.secret,
508
- from: this.crypto.getAgentSealerSecret(agentSecret),
509
- to: this.crypto.getAgentSealerID(accountAgentID),
510
- nOnceMaterial: {
511
- in: account.id,
512
- tx: account.core.nextTransactionID(),
513
- },
514
- });
515
-
516
- account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
517
-
518
- account.set("readKey", readKey.id, "trusting");
519
-
520
- const accountOnThisNode = this.expectCoValueLoaded(account.id);
521
-
522
- accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
523
-
524
- accountOnThisNode._cachedContent = undefined;
525
-
526
- return new RawControlledAccount(accountOnThisNode, agentSecret);
527
- }
528
-
529
541
  /** @internal */
530
542
  resolveAccountAgent(
531
543
  id: RawAccountID | AgentID,
@@ -535,7 +547,7 @@ export class LocalNode {
535
547
  return ok(id);
536
548
  }
537
549
 
538
- let coValue: CoValueCore;
550
+ let coValue: AvailableCoValueCore;
539
551
 
540
552
  try {
541
553
  coValue = this.expectCoValueLoaded(id, expectation);
@@ -549,11 +561,11 @@ export class LocalNode {
549
561
  }
550
562
 
551
563
  if (
552
- coValue.header.type !== "comap" ||
553
- coValue.header.ruleset.type !== "group" ||
554
- !coValue.header.meta ||
555
- !("type" in coValue.header.meta) ||
556
- 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"
557
569
  ) {
558
570
  return err({
559
571
  type: "UnexpectedlyNotAccount",
@@ -565,75 +577,30 @@ export class LocalNode {
565
577
  return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
566
578
  }
567
579
 
568
- resolveAccountAgentAsync(
569
- id: RawAccountID | AgentID,
570
- expectation?: string,
571
- ): ResultAsync<AgentID, ResolveAccountAgentError> {
572
- if (isAgentID(id)) {
573
- return okAsync(id);
574
- }
575
-
576
- return ResultAsync.fromPromise(
577
- this.loadCoValueCore(id),
578
- (e) =>
579
- ({
580
- type: "ErrorLoadingCoValueCore",
581
- expectation,
582
- id,
583
- error: e,
584
- }) satisfies LoadCoValueCoreError,
585
- ).andThen((coValue) => {
586
- if (coValue === "unavailable") {
587
- return err({
588
- type: "AccountUnavailableFromAllPeers" as const,
589
- expectation,
590
- id,
591
- } satisfies AccountUnavailableFromAllPeersError);
592
- }
593
-
594
- if (
595
- coValue.header.type !== "comap" ||
596
- coValue.header.ruleset.type !== "group" ||
597
- !coValue.header.meta ||
598
- !("type" in coValue.header.meta) ||
599
- coValue.header.meta.type !== "account"
600
- ) {
601
- return err({
602
- type: "UnexpectedlyNotAccount" as const,
603
- expectation,
604
- id,
605
- } satisfies UnexpectedlyNotAccountError);
606
- }
607
-
608
- return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
609
- });
610
- }
611
-
612
- /**
613
- * @deprecated use Account.createGroup() instead
614
- */
615
580
  createGroup(
616
581
  uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
617
582
  ): RawGroup {
583
+ const account = this.getCurrentAgent();
584
+
618
585
  const groupCoValue = this.createCoValue({
619
586
  type: "comap",
620
- ruleset: { type: "group", initialAdmin: this.account.id },
587
+ ruleset: { type: "group", initialAdmin: account.id },
621
588
  meta: null,
622
589
  ...uniqueness,
623
590
  });
624
591
 
625
592
  const group = expectGroup(groupCoValue.getCurrentContent());
626
593
 
627
- group.set(this.account.id, "admin", "trusting");
594
+ group.set(account.id, "admin", "trusting");
628
595
 
629
596
  const readKey = this.crypto.newRandomKeySecret();
630
597
 
631
598
  group.set(
632
- `${readKey.id}_for_${this.account.id}`,
599
+ `${readKey.id}_for_${account.id}`,
633
600
  this.crypto.seal({
634
601
  message: readKey.secret,
635
- from: this.account.currentSealerSecret(),
636
- to: this.account.currentSealerID(),
602
+ from: account.currentSealerSecret(),
603
+ to: account.currentSealerID(),
637
604
  nOnceMaterial: {
638
605
  in: groupCoValue.id,
639
606
  tx: groupCoValue.nextTransactionID(),
@@ -648,24 +615,34 @@ export class LocalNode {
648
615
  }
649
616
 
650
617
  /** @internal */
651
- testWithDifferentAccount(
652
- account: ControlledAccountOrAgent,
653
- currentSessionID: SessionID,
618
+ cloneWithDifferentAccount(
619
+ controlledAccountOrAgent: ControlledAccountOrAgent,
654
620
  ): LocalNode {
655
- 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
+ }
656
631
 
657
- const coValuesToCopy = Array.from(this.coValuesStore.getEntries());
632
+ /** @internal */
633
+ cloneVerifiedStateFrom(otherNode: LocalNode) {
634
+ const coValuesToCopy = Array.from(otherNode.coValues.entries());
658
635
 
659
636
  while (coValuesToCopy.length > 0) {
660
- const [coValueID, entry] = coValuesToCopy[coValuesToCopy.length - 1]!;
637
+ const [coValueID, coValue] = coValuesToCopy[coValuesToCopy.length - 1]!;
661
638
 
662
- if (!entry.isAvailable()) {
639
+ if (!coValue.isAvailable()) {
663
640
  coValuesToCopy.pop();
664
641
  continue;
665
642
  } else {
666
- const allDepsCopied = entry.core
643
+ const allDepsCopied = coValue
667
644
  .getDependedOnCoValues()
668
- .every((dep) => newNode.coValuesStore.get(dep).isAvailable());
645
+ .every((dep) => this.coValues.get(dep)?.isAvailable());
669
646
 
670
647
  if (!allDepsCopied) {
671
648
  // move to end of queue
@@ -673,34 +650,11 @@ export class LocalNode {
673
650
  continue;
674
651
  }
675
652
 
676
- const newCoValue = new CoValueCore(
677
- entry.core.header,
678
- newNode,
679
- new Map(entry.core.sessionLogs),
680
- );
681
-
682
- newNode.coValuesStore.internalMarkMagicallyAvailable(
683
- coValueID,
684
- newCoValue,
685
- );
653
+ this.putCoValue(coValueID, coValue.verified);
686
654
 
687
655
  coValuesToCopy.pop();
688
656
  }
689
657
  }
690
-
691
- if (account instanceof RawControlledAccount) {
692
- // To make sure that when we edit the account, we're modifying the correct sessions
693
- const accountInNode = new RawControlledAccount(
694
- newNode.expectCoValueLoaded(account.id),
695
- account.agentSecret,
696
- );
697
- if (accountInNode.core.node !== newNode) {
698
- throw new Error("Account's node is not the new node");
699
- }
700
- newNode.account = accountInNode;
701
- }
702
-
703
- return newNode;
704
658
  }
705
659
 
706
660
  gracefulShutdown() {