cojson 0.8.12 → 0.8.17

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 (164) hide show
  1. package/CHANGELOG.md +95 -83
  2. package/dist/native/PeerKnownStates.js +6 -1
  3. package/dist/native/PeerKnownStates.js.map +1 -1
  4. package/dist/native/PeerState.js +4 -3
  5. package/dist/native/PeerState.js.map +1 -1
  6. package/dist/native/PriorityBasedMessageQueue.js +1 -10
  7. package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
  8. package/dist/native/SyncStateSubscriptionManager.js +70 -0
  9. package/dist/native/SyncStateSubscriptionManager.js.map +1 -0
  10. package/dist/native/base64url.js.map +1 -1
  11. package/dist/native/base64url.test.js +1 -1
  12. package/dist/native/base64url.test.js.map +1 -1
  13. package/dist/native/coValue.js.map +1 -1
  14. package/dist/native/coValueCore.js +141 -149
  15. package/dist/native/coValueCore.js.map +1 -1
  16. package/dist/native/coValueState.js.map +1 -1
  17. package/dist/native/coValues/account.js +6 -6
  18. package/dist/native/coValues/account.js.map +1 -1
  19. package/dist/native/coValues/coList.js +2 -3
  20. package/dist/native/coValues/coList.js.map +1 -1
  21. package/dist/native/coValues/coMap.js +1 -1
  22. package/dist/native/coValues/coMap.js.map +1 -1
  23. package/dist/native/coValues/coStream.js +3 -5
  24. package/dist/native/coValues/coStream.js.map +1 -1
  25. package/dist/native/coValues/group.js +11 -11
  26. package/dist/native/coValues/group.js.map +1 -1
  27. package/dist/native/coreToCoValue.js +2 -2
  28. package/dist/native/coreToCoValue.js.map +1 -1
  29. package/dist/native/crypto/PureJSCrypto.js +4 -4
  30. package/dist/native/crypto/PureJSCrypto.js.map +1 -1
  31. package/dist/native/crypto/crypto.js.map +1 -1
  32. package/dist/native/exports.js +12 -12
  33. package/dist/native/exports.js.map +1 -1
  34. package/dist/native/ids.js.map +1 -1
  35. package/dist/native/jsonStringify.js.map +1 -1
  36. package/dist/native/localNode.js +5 -7
  37. package/dist/native/localNode.js.map +1 -1
  38. package/dist/native/permissions.js +4 -7
  39. package/dist/native/permissions.js.map +1 -1
  40. package/dist/native/priority.js.map +1 -1
  41. package/dist/native/storage/FileSystem.js.map +1 -1
  42. package/dist/native/storage/chunksAndKnownStates.js +2 -4
  43. package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
  44. package/dist/native/storage/index.js +6 -15
  45. package/dist/native/storage/index.js.map +1 -1
  46. package/dist/native/streamUtils.js.map +1 -1
  47. package/dist/native/sync.js +57 -7
  48. package/dist/native/sync.js.map +1 -1
  49. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  50. package/dist/native/typeUtils/expectGroup.js.map +1 -1
  51. package/dist/native/typeUtils/isAccountID.js.map +1 -1
  52. package/dist/native/typeUtils/isCoValue.js +1 -1
  53. package/dist/native/typeUtils/isCoValue.js.map +1 -1
  54. package/dist/web/PeerKnownStates.js +6 -1
  55. package/dist/web/PeerKnownStates.js.map +1 -1
  56. package/dist/web/PeerState.js +4 -3
  57. package/dist/web/PeerState.js.map +1 -1
  58. package/dist/web/PriorityBasedMessageQueue.js +1 -10
  59. package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
  60. package/dist/web/SyncStateSubscriptionManager.js +70 -0
  61. package/dist/web/SyncStateSubscriptionManager.js.map +1 -0
  62. package/dist/web/base64url.js.map +1 -1
  63. package/dist/web/base64url.test.js +1 -1
  64. package/dist/web/base64url.test.js.map +1 -1
  65. package/dist/web/coValue.js.map +1 -1
  66. package/dist/web/coValueCore.js +141 -149
  67. package/dist/web/coValueCore.js.map +1 -1
  68. package/dist/web/coValueState.js.map +1 -1
  69. package/dist/web/coValues/account.js +6 -6
  70. package/dist/web/coValues/account.js.map +1 -1
  71. package/dist/web/coValues/coList.js +2 -3
  72. package/dist/web/coValues/coList.js.map +1 -1
  73. package/dist/web/coValues/coMap.js +1 -1
  74. package/dist/web/coValues/coMap.js.map +1 -1
  75. package/dist/web/coValues/coStream.js +3 -5
  76. package/dist/web/coValues/coStream.js.map +1 -1
  77. package/dist/web/coValues/group.js +11 -11
  78. package/dist/web/coValues/group.js.map +1 -1
  79. package/dist/web/coreToCoValue.js +2 -2
  80. package/dist/web/coreToCoValue.js.map +1 -1
  81. package/dist/web/crypto/PureJSCrypto.js +4 -4
  82. package/dist/web/crypto/PureJSCrypto.js.map +1 -1
  83. package/dist/web/crypto/WasmCrypto.js +5 -5
  84. package/dist/web/crypto/WasmCrypto.js.map +1 -1
  85. package/dist/web/crypto/crypto.js.map +1 -1
  86. package/dist/web/exports.js +12 -12
  87. package/dist/web/exports.js.map +1 -1
  88. package/dist/web/ids.js.map +1 -1
  89. package/dist/web/jsonStringify.js.map +1 -1
  90. package/dist/web/localNode.js +5 -7
  91. package/dist/web/localNode.js.map +1 -1
  92. package/dist/web/permissions.js +4 -7
  93. package/dist/web/permissions.js.map +1 -1
  94. package/dist/web/priority.js.map +1 -1
  95. package/dist/web/storage/FileSystem.js.map +1 -1
  96. package/dist/web/storage/chunksAndKnownStates.js +2 -4
  97. package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
  98. package/dist/web/storage/index.js +6 -15
  99. package/dist/web/storage/index.js.map +1 -1
  100. package/dist/web/streamUtils.js.map +1 -1
  101. package/dist/web/sync.js +57 -7
  102. package/dist/web/sync.js.map +1 -1
  103. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  104. package/dist/web/typeUtils/expectGroup.js.map +1 -1
  105. package/dist/web/typeUtils/isAccountID.js.map +1 -1
  106. package/dist/web/typeUtils/isCoValue.js +1 -1
  107. package/dist/web/typeUtils/isCoValue.js.map +1 -1
  108. package/package.json +4 -14
  109. package/src/PeerKnownStates.ts +98 -90
  110. package/src/PeerState.ts +92 -73
  111. package/src/PriorityBasedMessageQueue.ts +42 -49
  112. package/src/SyncStateSubscriptionManager.ts +124 -0
  113. package/src/base64url.test.ts +24 -24
  114. package/src/base64url.ts +44 -45
  115. package/src/coValue.ts +45 -45
  116. package/src/coValueCore.ts +746 -785
  117. package/src/coValueState.ts +82 -72
  118. package/src/coValues/account.ts +143 -150
  119. package/src/coValues/coList.ts +520 -522
  120. package/src/coValues/coMap.ts +283 -285
  121. package/src/coValues/coStream.ts +320 -324
  122. package/src/coValues/group.ts +306 -305
  123. package/src/coreToCoValue.ts +28 -31
  124. package/src/crypto/PureJSCrypto.ts +188 -194
  125. package/src/crypto/WasmCrypto.ts +236 -254
  126. package/src/crypto/crypto.ts +302 -309
  127. package/src/exports.ts +116 -116
  128. package/src/ids.ts +9 -9
  129. package/src/jsonStringify.ts +46 -46
  130. package/src/jsonValue.ts +24 -10
  131. package/src/localNode.ts +635 -660
  132. package/src/media.ts +3 -3
  133. package/src/permissions.ts +272 -278
  134. package/src/priority.ts +21 -19
  135. package/src/storage/FileSystem.ts +91 -99
  136. package/src/storage/chunksAndKnownStates.ts +110 -115
  137. package/src/storage/index.ts +466 -497
  138. package/src/streamUtils.ts +60 -60
  139. package/src/sync.ts +656 -608
  140. package/src/tests/PeerKnownStates.test.ts +38 -34
  141. package/src/tests/PeerState.test.ts +101 -64
  142. package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
  143. package/src/tests/SyncStateSubscriptionManager.test.ts +232 -0
  144. package/src/tests/account.test.ts +59 -59
  145. package/src/tests/coList.test.ts +65 -65
  146. package/src/tests/coMap.test.ts +137 -137
  147. package/src/tests/coStream.test.ts +254 -257
  148. package/src/tests/coValueCore.test.ts +153 -156
  149. package/src/tests/crypto.test.ts +136 -144
  150. package/src/tests/cryptoImpl.test.ts +205 -197
  151. package/src/tests/group.test.ts +24 -24
  152. package/src/tests/permissions.test.ts +1306 -1371
  153. package/src/tests/priority.test.ts +65 -82
  154. package/src/tests/sync.test.ts +1573 -1263
  155. package/src/tests/testUtils.ts +85 -53
  156. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
  157. package/src/typeUtils/expectGroup.ts +9 -9
  158. package/src/typeUtils/isAccountID.ts +1 -1
  159. package/src/typeUtils/isCoValue.ts +9 -9
  160. package/tsconfig.json +4 -6
  161. package/tsconfig.native.json +9 -11
  162. package/tsconfig.web.json +4 -10
  163. package/.eslintrc.cjs +0 -25
  164. package/.prettierrc.js +0 -9
package/src/localNode.ts CHANGED
@@ -1,34 +1,34 @@
1
- import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
1
+ import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
2
+ import { CoID } from "./coValue.js";
3
+ import { RawCoValue } from "./coValue.js";
2
4
  import {
3
- CoValueCore,
4
- CoValueHeader,
5
- CoValueUniqueness,
5
+ CoValueCore,
6
+ CoValueHeader,
7
+ CoValueUniqueness,
6
8
  } from "./coValueCore.js";
9
+ import { CoValueState } from "./coValueState.js";
10
+ import {
11
+ AccountMeta,
12
+ ControlledAccountOrAgent,
13
+ ControlledAgent,
14
+ InvalidAccountAgentIDError,
15
+ RawProfile as Profile,
16
+ RawAccount,
17
+ RawAccountID,
18
+ RawAccountMigration,
19
+ RawControlledAccount,
20
+ RawProfile,
21
+ accountHeaderForInitialAgentSecret,
22
+ } from "./coValues/account.js";
7
23
  import {
8
- InviteSecret,
9
- RawGroup,
10
- secretSeedFromInviteSecret,
24
+ InviteSecret,
25
+ RawGroup,
26
+ secretSeedFromInviteSecret,
11
27
  } from "./coValues/group.js";
12
- import { Peer, PeerID, SyncManager } from "./sync.js";
28
+ import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
13
29
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
14
- import { CoID } from "./coValue.js";
15
- import {
16
- RawAccount,
17
- AccountMeta,
18
- accountHeaderForInitialAgentSecret,
19
- ControlledAccountOrAgent,
20
- RawControlledAccount,
21
- ControlledAgent,
22
- RawAccountID,
23
- RawProfile,
24
- RawProfile as Profile,
25
- RawAccountMigration,
26
- InvalidAccountAgentIDError,
27
- } from "./coValues/account.js";
28
- import { RawCoValue } from "./coValue.js";
30
+ import { Peer, PeerID, SyncManager } from "./sync.js";
29
31
  import { expectGroup } from "./typeUtils/expectGroup.js";
30
- import { err, ok, okAsync, Result, ResultAsync } from "neverthrow";
31
- import { CoValueState } from "./coValueState.js";
32
32
 
33
33
  /** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
34
34
 
@@ -42,712 +42,687 @@ const { localNode } = useJazz();
42
42
  ```
43
43
  */
44
44
  export class LocalNode {
45
- /** @internal */
45
+ /** @internal */
46
+ crypto: CryptoProvider;
47
+ /** @internal */
48
+ coValues: { [key: RawCoID]: CoValueState } = {};
49
+ /** @category 3. Low-level */
50
+ account: ControlledAccountOrAgent;
51
+ /** @category 3. Low-level */
52
+ currentSessionID: SessionID;
53
+ /** @category 3. Low-level */
54
+ syncManager = new SyncManager(this);
55
+
56
+ crashed: Error | undefined = undefined;
57
+
58
+ /** @category 3. Low-level */
59
+ constructor(
60
+ account: ControlledAccountOrAgent,
61
+ currentSessionID: SessionID,
62
+ crypto: CryptoProvider,
63
+ ) {
64
+ this.account = account;
65
+ this.currentSessionID = currentSessionID;
66
+ this.crypto = crypto;
67
+ }
68
+
69
+ /** @category 2. Node Creation */
70
+ static async withNewlyCreatedAccount<Meta extends AccountMeta = AccountMeta>({
71
+ creationProps,
72
+ peersToLoadFrom,
73
+ migration,
74
+ crypto,
75
+ initialAgentSecret = crypto.newRandomAgentSecret(),
76
+ }: {
77
+ creationProps: { name: string };
78
+ peersToLoadFrom?: Peer[];
79
+ migration?: RawAccountMigration<Meta>;
46
80
  crypto: CryptoProvider;
47
- /** @internal */
48
- coValues: { [key: RawCoID]: CoValueState } = {};
49
- /** @category 3. Low-level */
50
- account: ControlledAccountOrAgent;
51
- /** @category 3. Low-level */
52
- currentSessionID: SessionID;
53
- /** @category 3. Low-level */
54
- syncManager = new SyncManager(this);
55
-
56
- crashed: Error | undefined = undefined;
57
-
58
- /** @category 3. Low-level */
59
- constructor(
60
- account: ControlledAccountOrAgent,
61
- currentSessionID: SessionID,
62
- crypto: CryptoProvider,
63
- ) {
64
- this.account = account;
65
- this.currentSessionID = currentSessionID;
66
- this.crypto = crypto;
81
+ initialAgentSecret?: AgentSecret;
82
+ }): Promise<{
83
+ node: LocalNode;
84
+ accountID: RawAccountID;
85
+ accountSecret: AgentSecret;
86
+ sessionID: SessionID;
87
+ }> {
88
+ const throwawayAgent = crypto.newRandomAgentSecret();
89
+ const setupNode = new LocalNode(
90
+ new ControlledAgent(throwawayAgent, crypto),
91
+ crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
92
+ crypto,
93
+ );
94
+
95
+ const account = setupNode.createAccount(initialAgentSecret);
96
+
97
+ const nodeWithAccount = account.core.node.testWithDifferentAccount(
98
+ account,
99
+ crypto.newRandomSessionID(account.id),
100
+ );
101
+
102
+ const accountOnNodeWithAccount =
103
+ nodeWithAccount.account as RawControlledAccount<Meta>;
104
+
105
+ if (peersToLoadFrom) {
106
+ for (const peer of peersToLoadFrom) {
107
+ nodeWithAccount.syncManager.addPeer(peer);
108
+ }
67
109
  }
68
110
 
69
- /** @category 2. Node Creation */
70
- static async withNewlyCreatedAccount<
71
- Meta extends AccountMeta = AccountMeta,
72
- >({
73
- creationProps,
74
- peersToLoadFrom,
75
- migration,
76
- crypto,
77
- initialAgentSecret = crypto.newRandomAgentSecret(),
78
- }: {
79
- creationProps: { name: string };
80
- peersToLoadFrom?: Peer[];
81
- migration?: RawAccountMigration<Meta>;
82
- crypto: CryptoProvider;
83
- initialAgentSecret?: AgentSecret;
84
- }): Promise<{
85
- node: LocalNode;
86
- accountID: RawAccountID;
87
- accountSecret: AgentSecret;
88
- sessionID: SessionID;
89
- }> {
90
- const throwawayAgent = crypto.newRandomAgentSecret();
91
- const setupNode = new LocalNode(
92
- new ControlledAgent(throwawayAgent, crypto),
93
- crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
94
- crypto,
95
- );
96
-
97
- const account = setupNode.createAccount(initialAgentSecret);
111
+ if (migration) {
112
+ await migration(accountOnNodeWithAccount, nodeWithAccount, creationProps);
113
+ } else {
114
+ const profileGroup = accountOnNodeWithAccount.createGroup();
115
+ profileGroup.addMember("everyone", "reader");
116
+ const profile = profileGroup.createMap<Profile>({
117
+ name: creationProps.name,
118
+ });
119
+ accountOnNodeWithAccount.set("profile", profile.id, "trusting");
120
+ }
98
121
 
99
- const nodeWithAccount = account.core.node.testWithDifferentAccount(
100
- account,
101
- crypto.newRandomSessionID(account.id),
102
- );
122
+ const controlledAccount = new RawControlledAccount(
123
+ accountOnNodeWithAccount.core,
124
+ accountOnNodeWithAccount.agentSecret,
125
+ );
103
126
 
104
- const accountOnNodeWithAccount =
105
- nodeWithAccount.account as RawControlledAccount<Meta>;
127
+ nodeWithAccount.account = controlledAccount;
128
+ nodeWithAccount.coValues[controlledAccount.id] = CoValueState.Available(
129
+ controlledAccount.core,
130
+ );
131
+ controlledAccount.core._cachedContent = undefined;
106
132
 
107
- if (peersToLoadFrom) {
108
- for (const peer of peersToLoadFrom) {
109
- nodeWithAccount.syncManager.addPeer(peer);
110
- }
111
- }
133
+ if (!controlledAccount.get("profile")) {
134
+ throw new Error("Must set account profile in initial migration");
135
+ }
112
136
 
113
- if (migration) {
114
- await migration(
115
- accountOnNodeWithAccount,
116
- nodeWithAccount,
117
- creationProps,
118
- );
119
- } else {
120
- const profileGroup = accountOnNodeWithAccount.createGroup();
121
- profileGroup.addMember("everyone", "reader");
122
- const profile = profileGroup.createMap<Profile>({
123
- name: creationProps.name,
124
- });
125
- accountOnNodeWithAccount.set("profile", profile.id, "trusting");
137
+ // we shouldn't need this, but it fixes account data not syncing for new accounts
138
+ function syncAllCoValuesAfterCreateAccount() {
139
+ for (const coValueEntry of Object.values(nodeWithAccount.coValues)) {
140
+ if (coValueEntry.state.type === "available") {
141
+ void nodeWithAccount.syncManager.syncCoValue(
142
+ coValueEntry.state.coValue,
143
+ );
126
144
  }
145
+ }
146
+ }
127
147
 
128
- const controlledAccount = new RawControlledAccount(
129
- accountOnNodeWithAccount.core,
130
- accountOnNodeWithAccount.agentSecret,
131
- );
148
+ syncAllCoValuesAfterCreateAccount();
149
+
150
+ setTimeout(syncAllCoValuesAfterCreateAccount, 500);
151
+
152
+ return {
153
+ node: nodeWithAccount,
154
+ accountID: accountOnNodeWithAccount.id,
155
+ accountSecret: accountOnNodeWithAccount.agentSecret,
156
+ sessionID: nodeWithAccount.currentSessionID,
157
+ };
158
+ }
159
+
160
+ /** @category 2. Node Creation */
161
+ static async withLoadedAccount<Meta extends AccountMeta = AccountMeta>({
162
+ accountID,
163
+ accountSecret,
164
+ sessionID,
165
+ peersToLoadFrom,
166
+ crypto,
167
+ migration,
168
+ }: {
169
+ accountID: RawAccountID;
170
+ accountSecret: AgentSecret;
171
+ sessionID: SessionID | undefined;
172
+ peersToLoadFrom: Peer[];
173
+ crypto: CryptoProvider;
174
+ migration?: RawAccountMigration<Meta>;
175
+ }): Promise<LocalNode> {
176
+ try {
177
+ const loadingNode = new LocalNode(
178
+ new ControlledAgent(accountSecret, crypto),
179
+ crypto.newRandomSessionID(accountID),
180
+ crypto,
181
+ );
132
182
 
133
- nodeWithAccount.account = controlledAccount;
134
- nodeWithAccount.coValues[controlledAccount.id] = CoValueState.Available(
135
- controlledAccount.core,
136
- );
137
- controlledAccount.core._cachedContent = undefined;
183
+ for (const peer of peersToLoadFrom) {
184
+ loadingNode.syncManager.addPeer(peer);
185
+ }
138
186
 
139
- if (!controlledAccount.get("profile")) {
140
- throw new Error("Must set account profile in initial migration");
141
- }
187
+ const accountPromise = loadingNode.load(accountID);
142
188
 
143
- // we shouldn't need this, but it fixes account data not syncing for new accounts
144
- function syncAllCoValuesAfterCreateAccount() {
145
- for (const coValueEntry of Object.values(
146
- nodeWithAccount.coValues,
147
- )) {
148
- if (coValueEntry.state.type === "available") {
149
- void nodeWithAccount.syncManager.syncCoValue(
150
- coValueEntry.state.coValue,
151
- );
152
- }
153
- }
154
- }
189
+ const account = await accountPromise;
155
190
 
156
- syncAllCoValuesAfterCreateAccount();
191
+ if (account === "unavailable") {
192
+ throw new Error("Account unavailable from all peers");
193
+ }
157
194
 
158
- setTimeout(syncAllCoValuesAfterCreateAccount, 500);
195
+ const controlledAccount = new RawControlledAccount(
196
+ account.core,
197
+ accountSecret,
198
+ );
199
+
200
+ // since this is all synchronous, we can just swap out nodes for the SyncManager
201
+ const node = loadingNode.testWithDifferentAccount(
202
+ controlledAccount,
203
+ sessionID || crypto.newRandomSessionID(accountID),
204
+ );
205
+ node.syncManager = loadingNode.syncManager;
206
+ node.syncManager.local = node;
207
+
208
+ controlledAccount.core.node = node;
209
+ node.coValues[accountID] = CoValueState.Available(controlledAccount.core);
210
+ controlledAccount.core._cachedContent = undefined;
211
+
212
+ const profileID = account.get("profile");
213
+ if (!profileID) {
214
+ throw new Error("Account has no profile");
215
+ }
216
+ const profile = await node.load(profileID);
217
+
218
+ if (profile === "unavailable") {
219
+ throw new Error("Profile unavailable from all peers");
220
+ }
221
+
222
+ if (migration) {
223
+ await migration(controlledAccount as RawControlledAccount<Meta>, node);
224
+ node.account = new RawControlledAccount(
225
+ controlledAccount.core,
226
+ controlledAccount.agentSecret,
227
+ );
228
+ }
159
229
 
160
- return {
161
- node: nodeWithAccount,
162
- accountID: accountOnNodeWithAccount.id,
163
- accountSecret: accountOnNodeWithAccount.agentSecret,
164
- sessionID: nodeWithAccount.currentSessionID,
165
- };
230
+ return node;
231
+ } catch (e) {
232
+ console.error("Error withLoadedAccount", e);
233
+ throw e;
234
+ }
235
+ }
236
+
237
+ /** @internal */
238
+ createCoValue(header: CoValueHeader): CoValueCore {
239
+ if (this.crashed) {
240
+ throw new Error("Trying to create CoValue after node has crashed", {
241
+ cause: this.crashed,
242
+ });
166
243
  }
167
244
 
168
- /** @category 2. Node Creation */
169
- static async withLoadedAccount<Meta extends AccountMeta = AccountMeta>({
170
- accountID,
171
- accountSecret,
172
- sessionID,
173
- peersToLoadFrom,
174
- crypto,
175
- migration,
176
- }: {
177
- accountID: RawAccountID;
178
- accountSecret: AgentSecret;
179
- sessionID: SessionID | undefined;
180
- peersToLoadFrom: Peer[];
181
- crypto: CryptoProvider;
182
- migration?: RawAccountMigration<Meta>;
183
- }): Promise<LocalNode> {
184
- try {
185
- const loadingNode = new LocalNode(
186
- new ControlledAgent(accountSecret, crypto),
187
- crypto.newRandomSessionID(accountID),
188
- crypto,
189
- );
190
-
191
- for (const peer of peersToLoadFrom) {
192
- loadingNode.syncManager.addPeer(peer);
193
- }
194
-
195
- const accountPromise = loadingNode.load(accountID);
196
-
197
- const account = await accountPromise;
198
-
199
- if (account === "unavailable") {
200
- throw new Error("Account unavailable from all peers");
201
- }
202
-
203
- const controlledAccount = new RawControlledAccount(
204
- account.core,
205
- accountSecret,
206
- );
207
-
208
- // since this is all synchronous, we can just swap out nodes for the SyncManager
209
- const node = loadingNode.testWithDifferentAccount(
210
- controlledAccount,
211
- sessionID || crypto.newRandomSessionID(accountID),
212
- );
213
- node.syncManager = loadingNode.syncManager;
214
- node.syncManager.local = node;
215
-
216
- controlledAccount.core.node = node;
217
- node.coValues[accountID] = CoValueState.Available(
218
- controlledAccount.core,
219
- );
220
- controlledAccount.core._cachedContent = undefined;
221
-
222
- const profileID = account.get("profile");
223
- if (!profileID) {
224
- throw new Error("Account has no profile");
225
- }
226
- const profile = await node.load(profileID);
227
-
228
- if (profile === "unavailable") {
229
- throw new Error("Profile unavailable from all peers");
230
- }
231
-
232
- if (migration) {
233
- await migration(
234
- controlledAccount as RawControlledAccount<Meta>,
235
- node,
236
- );
237
- node.account = new RawControlledAccount(
238
- controlledAccount.core,
239
- controlledAccount.agentSecret,
240
- );
241
- }
242
-
243
- return node;
244
- } catch (e) {
245
- console.error("Error withLoadedAccount", e);
246
- throw e;
247
- }
245
+ const coValue = new CoValueCore(header, this);
246
+ this.coValues[coValue.id] = CoValueState.Available(coValue);
247
+
248
+ void this.syncManager.syncCoValue(coValue);
249
+
250
+ return coValue;
251
+ }
252
+
253
+ /** @internal */
254
+ async loadCoValueCore(
255
+ id: RawCoID,
256
+ options: {
257
+ dontLoadFrom?: PeerID;
258
+ dontWaitFor?: PeerID;
259
+ } = {},
260
+ ): Promise<CoValueCore | "unavailable"> {
261
+ if (this.crashed) {
262
+ throw new Error("Trying to load CoValue after node has crashed", {
263
+ cause: this.crashed,
264
+ });
248
265
  }
249
266
 
250
- /** @internal */
251
- createCoValue(header: CoValueHeader): CoValueCore {
252
- if (this.crashed) {
253
- throw new Error("Trying to create CoValue after node has crashed", {
254
- cause: this.crashed,
255
- });
256
- }
267
+ let entry = this.coValues[id];
268
+ if (!entry) {
269
+ const peersToWaitFor = new Set(
270
+ Object.values(this.syncManager.peers)
271
+ .filter((peer) => peer.isServerOrStoragePeer())
272
+ .map((peer) => peer.id),
273
+ );
274
+ if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
275
+ entry = CoValueState.Unknown(peersToWaitFor);
257
276
 
258
- const coValue = new CoValueCore(header, this);
259
- this.coValues[coValue.id] = CoValueState.Available(coValue);
277
+ this.coValues[id] = entry;
260
278
 
261
- void this.syncManager.syncCoValue(coValue);
279
+ this.syncManager.loadFromPeers(id, options.dontLoadFrom).catch((e) => {
280
+ console.error(
281
+ "Error loading from peers",
282
+ id,
262
283
 
263
- return coValue;
284
+ e,
285
+ );
286
+ });
287
+ }
288
+ if (entry.state.type === "available") {
289
+ return Promise.resolve(entry.state.coValue);
264
290
  }
265
291
 
266
- /** @internal */
267
- async loadCoValueCore(
268
- id: RawCoID,
269
- options: {
270
- dontLoadFrom?: PeerID;
271
- dontWaitFor?: PeerID;
272
- } = {},
273
- ): Promise<CoValueCore | "unavailable"> {
274
- if (this.crashed) {
275
- throw new Error("Trying to load CoValue after node has crashed", {
276
- cause: this.crashed,
277
- });
278
- }
279
-
280
- let entry = this.coValues[id];
281
- if (!entry) {
282
- const peersToWaitFor = new Set(
283
- Object.values(this.syncManager.peers)
284
- .filter((peer) => peer.isServerOrStoragePeer())
285
- .map((peer) => peer.id),
286
- );
287
- if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
288
- entry = CoValueState.Unknown(peersToWaitFor);
289
-
290
- this.coValues[id] = entry;
291
-
292
- this.syncManager
293
- .loadFromPeers(id, options.dontLoadFrom)
294
- .catch((e) => {
295
- console.error(
296
- "Error loading from peers",
297
- id,
298
-
299
- e,
300
- );
301
- });
302
- }
303
- if (entry.state.type === "available") {
304
- return Promise.resolve(entry.state.coValue);
305
- }
306
-
307
- await entry.state.ready;
292
+ await entry.state.ready;
308
293
 
309
- const updatedEntry = this.coValues[id];
294
+ const updatedEntry = this.coValues[id];
310
295
 
311
- if (updatedEntry?.state.type === "available") {
312
- return Promise.resolve(updatedEntry.state.coValue);
313
- }
296
+ if (updatedEntry?.state.type === "available") {
297
+ return Promise.resolve(updatedEntry.state.coValue);
298
+ }
314
299
 
315
- return "unavailable";
300
+ return "unavailable";
301
+ }
302
+
303
+ /**
304
+ * Loads a CoValue's content, syncing from peers as necessary and resolving the returned
305
+ * promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
306
+ * for listening to subsequent updates to the CoValue.
307
+ *
308
+ * @category 3. Low-level
309
+ */
310
+ async load<T extends RawCoValue>(id: CoID<T>): Promise<T | "unavailable"> {
311
+ const core = await this.loadCoValueCore(id);
312
+
313
+ if (core === "unavailable") {
314
+ return "unavailable";
316
315
  }
317
316
 
318
- /**
319
- * Loads a CoValue's content, syncing from peers as necessary and resolving the returned
320
- * promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
321
- * for listening to subsequent updates to the CoValue.
322
- *
323
- * @category 3. Low-level
324
- */
325
- async load<T extends RawCoValue>(id: CoID<T>): Promise<T | "unavailable"> {
326
- const core = await this.loadCoValueCore(id);
327
-
328
- if (core === "unavailable") {
329
- return "unavailable";
330
- }
317
+ return core.getCurrentContent() as T;
318
+ }
331
319
 
332
- return core.getCurrentContent() as T;
320
+ getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
321
+ const entry = this.coValues[id];
322
+ if (!entry) {
323
+ return undefined;
333
324
  }
334
-
335
- getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
336
- const entry = this.coValues[id];
337
- if (!entry) {
338
- return undefined;
325
+ if (entry.state.type === "available") {
326
+ return entry.state.coValue.getCurrentContent() as T;
327
+ }
328
+ return undefined;
329
+ }
330
+
331
+ /** @category 3. Low-level */
332
+ subscribe<T extends RawCoValue>(
333
+ id: CoID<T>,
334
+ callback: (update: T | "unavailable") => void,
335
+ ): () => void {
336
+ let stopped = false;
337
+ let unsubscribe!: () => void;
338
+
339
+ // console.log("Subscribing to " + id);
340
+
341
+ this.load(id)
342
+ .then((coValue) => {
343
+ if (stopped) {
344
+ return;
339
345
  }
340
- if (entry.state.type === "available") {
341
- return entry.state.coValue.getCurrentContent() as T;
346
+ if (coValue === "unavailable") {
347
+ callback("unavailable");
348
+ return;
342
349
  }
343
- return undefined;
350
+ unsubscribe = coValue.subscribe(callback);
351
+ })
352
+ .catch((e) => {
353
+ console.error("Error subscribing to ", id, e);
354
+ });
355
+
356
+ return () => {
357
+ console.log("Unsubscribing from " + id);
358
+ stopped = true;
359
+ unsubscribe?.();
360
+ };
361
+ }
362
+
363
+ /** @deprecated Use Account.acceptInvite instead */
364
+ async acceptInvite<T extends RawCoValue>(
365
+ groupOrOwnedValueID: CoID<T>,
366
+ inviteSecret: InviteSecret,
367
+ ): Promise<void> {
368
+ const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
369
+
370
+ if (groupOrOwnedValue === "unavailable") {
371
+ throw new Error(
372
+ "Trying to accept invite: Group/owned value unavailable from all peers",
373
+ );
344
374
  }
345
375
 
346
- /** @category 3. Low-level */
347
- subscribe<T extends RawCoValue>(
348
- id: CoID<T>,
349
- callback: (update: T | "unavailable") => void,
350
- ): () => void {
351
- let stopped = false;
352
- let unsubscribe!: () => void;
353
-
354
- // console.log("Subscribing to " + id);
355
-
356
- this.load(id)
357
- .then((coValue) => {
358
- if (stopped) {
359
- return;
360
- }
361
- if (coValue === "unavailable") {
362
- callback("unavailable");
363
- return;
364
- }
365
- unsubscribe = coValue.subscribe(callback);
366
- })
367
- .catch((e) => {
368
- console.error("Error subscribing to ", id, e);
369
- });
370
-
371
- return () => {
372
- console.log("Unsubscribing from " + id);
373
- stopped = true;
374
- unsubscribe?.();
375
- };
376
+ if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
377
+ return this.acceptInvite(
378
+ groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
379
+ inviteSecret,
380
+ );
381
+ } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
382
+ throw new Error("Can only accept invites to groups");
376
383
  }
377
384
 
378
- /** @deprecated Use Account.acceptInvite instead */
379
- async acceptInvite<T extends RawCoValue>(
380
- groupOrOwnedValueID: CoID<T>,
381
- inviteSecret: InviteSecret,
382
- ): Promise<void> {
383
- const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
384
-
385
- if (groupOrOwnedValue === "unavailable") {
386
- throw new Error(
387
- "Trying to accept invite: Group/owned value unavailable from all peers",
388
- );
389
- }
390
-
391
- if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
392
- return this.acceptInvite(
393
- groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
394
- inviteSecret,
395
- );
396
- } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
397
- throw new Error("Can only accept invites to groups");
398
- }
399
-
400
- const group = expectGroup(groupOrOwnedValue);
385
+ const group = expectGroup(groupOrOwnedValue);
401
386
 
402
- const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
403
- secretSeedFromInviteSecret(inviteSecret),
404
- );
405
- const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
406
-
407
- const inviteRole = await new Promise((resolve, reject) => {
408
- group.subscribe((groupUpdate) => {
409
- const role = groupUpdate.get(inviteAgentID);
410
- if (role) {
411
- resolve(role);
412
- }
413
- });
414
- setTimeout(
415
- () => reject(new Error("Couldn't find invite before timeout")),
416
- 2000,
417
- );
418
- });
419
-
420
- if (!inviteRole) {
421
- throw new Error("No invite found");
422
- }
387
+ const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
388
+ secretSeedFromInviteSecret(inviteSecret),
389
+ );
390
+ const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
423
391
 
424
- const existingRole = group.get(this.account.id);
425
-
426
- if (
427
- existingRole === "admin" ||
428
- (existingRole === "writer" && inviteRole === "writerInvite") ||
429
- (existingRole === "writer" && inviteRole === "reader") ||
430
- (existingRole === "reader" && inviteRole === "readerInvite")
431
- ) {
432
- console.debug(
433
- "Not accepting invite that would replace or downgrade role",
434
- );
435
- return;
392
+ const inviteRole = await new Promise((resolve, reject) => {
393
+ group.subscribe((groupUpdate) => {
394
+ const role = groupUpdate.get(inviteAgentID);
395
+ if (role) {
396
+ resolve(role);
436
397
  }
398
+ });
399
+ setTimeout(
400
+ () => reject(new Error("Couldn't find invite before timeout")),
401
+ 2000,
402
+ );
403
+ });
404
+
405
+ if (!inviteRole) {
406
+ throw new Error("No invite found");
407
+ }
437
408
 
438
- const groupAsInvite = expectGroup(
439
- group.core
440
- .testWithDifferentAccount(
441
- new ControlledAgent(inviteAgentSecret, this.crypto),
442
- this.crypto.newRandomSessionID(inviteAgentID),
443
- )
444
- .getCurrentContent(),
445
- );
446
-
447
- groupAsInvite.addMemberInternal(
448
- this.account,
449
- inviteRole === "adminInvite"
450
- ? "admin"
451
- : inviteRole === "writerInvite"
452
- ? "writer"
453
- : "reader",
454
- );
455
-
456
- group.core._sessionLogs = groupAsInvite.core.sessionLogs;
457
- group.core._cachedContent = undefined;
409
+ const existingRole = group.get(this.account.id);
458
410
 
459
- for (const groupListener of group.core.listeners) {
460
- groupListener(group.core.getCurrentContent());
461
- }
411
+ if (
412
+ existingRole === "admin" ||
413
+ (existingRole === "writer" && inviteRole === "writerInvite") ||
414
+ (existingRole === "writer" && inviteRole === "reader") ||
415
+ (existingRole === "reader" && inviteRole === "readerInvite")
416
+ ) {
417
+ console.debug(
418
+ "Not accepting invite that would replace or downgrade role",
419
+ );
420
+ return;
462
421
  }
463
422
 
464
- /** @internal */
465
- expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
466
- const entry = this.coValues[id];
467
- if (!entry) {
468
- throw new Error(
469
- `${expectation ? expectation + ": " : ""}Unknown CoValue ${id}`,
470
- );
471
- }
472
- if (entry.state.type === "unknown") {
473
- throw new Error(
474
- `${
475
- expectation ? expectation + ": " : ""
476
- }CoValue ${id} not yet loaded`,
477
- );
478
- }
479
- return entry.state.coValue;
423
+ const groupAsInvite = expectGroup(
424
+ group.core
425
+ .testWithDifferentAccount(
426
+ new ControlledAgent(inviteAgentSecret, this.crypto),
427
+ this.crypto.newRandomSessionID(inviteAgentID),
428
+ )
429
+ .getCurrentContent(),
430
+ );
431
+
432
+ groupAsInvite.addMemberInternal(
433
+ this.account,
434
+ inviteRole === "adminInvite"
435
+ ? "admin"
436
+ : inviteRole === "writerInvite"
437
+ ? "writer"
438
+ : "reader",
439
+ );
440
+
441
+ group.core._sessionLogs = groupAsInvite.core.sessionLogs;
442
+ group.core._cachedContent = undefined;
443
+
444
+ for (const groupListener of group.core.listeners) {
445
+ groupListener(group.core.getCurrentContent());
480
446
  }
481
-
482
- /** @internal */
483
- expectProfileLoaded(id: RawAccountID, expectation?: string): RawProfile {
484
- const account = this.expectCoValueLoaded(id, expectation);
485
- const profileID = expectGroup(account.getCurrentContent()).get(
486
- "profile",
487
- );
488
- if (!profileID) {
489
- throw new Error(
490
- `${
491
- expectation ? expectation + ": " : ""
492
- }Account ${id} has no profile`,
493
- );
494
- }
495
- return this.expectCoValueLoaded(
496
- profileID,
497
- expectation,
498
- ).getCurrentContent() as RawProfile;
447
+ }
448
+
449
+ /** @internal */
450
+ expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
451
+ const entry = this.coValues[id];
452
+ if (!entry) {
453
+ throw new Error(
454
+ `${expectation ? expectation + ": " : ""}Unknown CoValue ${id}`,
455
+ );
499
456
  }
500
-
501
- /** @internal */
502
- createAccount(
503
- agentSecret = this.crypto.newRandomAgentSecret(),
504
- ): RawControlledAccount {
505
- const accountAgentID = this.crypto.getAgentID(agentSecret);
506
- const account = expectGroup(
507
- this.createCoValue(
508
- accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
509
- )
510
- .testWithDifferentAccount(
511
- new ControlledAgent(agentSecret, this.crypto),
512
- this.crypto.newRandomSessionID(accountAgentID),
513
- )
514
- .getCurrentContent(),
515
- );
516
-
517
- account.set(accountAgentID, "admin", "trusting");
518
-
519
- const readKey = this.crypto.newRandomKeySecret();
520
-
521
- const sealed = this.crypto.seal({
522
- message: readKey.secret,
523
- from: this.crypto.getAgentSealerSecret(agentSecret),
524
- to: this.crypto.getAgentSealerID(accountAgentID),
525
- nOnceMaterial: {
526
- in: account.id,
527
- tx: account.core.nextTransactionID(),
528
- },
529
- });
530
-
531
- account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
532
-
533
- account.set("readKey", readKey.id, "trusting");
534
-
535
- const accountOnThisNode = this.expectCoValueLoaded(account.id);
536
-
537
- accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
538
-
539
- accountOnThisNode._cachedContent = undefined;
540
-
541
- return new RawControlledAccount(accountOnThisNode, agentSecret);
457
+ if (entry.state.type === "unknown") {
458
+ throw new Error(
459
+ `${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded`,
460
+ );
461
+ }
462
+ return entry.state.coValue;
463
+ }
464
+
465
+ /** @internal */
466
+ expectProfileLoaded(id: RawAccountID, expectation?: string): RawProfile {
467
+ const account = this.expectCoValueLoaded(id, expectation);
468
+ const profileID = expectGroup(account.getCurrentContent()).get("profile");
469
+ if (!profileID) {
470
+ throw new Error(
471
+ `${expectation ? expectation + ": " : ""}Account ${id} has no profile`,
472
+ );
473
+ }
474
+ return this.expectCoValueLoaded(
475
+ profileID,
476
+ expectation,
477
+ ).getCurrentContent() as RawProfile;
478
+ }
479
+
480
+ /** @internal */
481
+ createAccount(
482
+ agentSecret = this.crypto.newRandomAgentSecret(),
483
+ ): RawControlledAccount {
484
+ const accountAgentID = this.crypto.getAgentID(agentSecret);
485
+ const account = expectGroup(
486
+ this.createCoValue(
487
+ accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
488
+ )
489
+ .testWithDifferentAccount(
490
+ new ControlledAgent(agentSecret, this.crypto),
491
+ this.crypto.newRandomSessionID(accountAgentID),
492
+ )
493
+ .getCurrentContent(),
494
+ );
495
+
496
+ account.set(accountAgentID, "admin", "trusting");
497
+
498
+ const readKey = this.crypto.newRandomKeySecret();
499
+
500
+ const sealed = this.crypto.seal({
501
+ message: readKey.secret,
502
+ from: this.crypto.getAgentSealerSecret(agentSecret),
503
+ to: this.crypto.getAgentSealerID(accountAgentID),
504
+ nOnceMaterial: {
505
+ in: account.id,
506
+ tx: account.core.nextTransactionID(),
507
+ },
508
+ });
509
+
510
+ account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
511
+
512
+ account.set("readKey", readKey.id, "trusting");
513
+
514
+ const accountOnThisNode = this.expectCoValueLoaded(account.id);
515
+
516
+ accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
517
+
518
+ accountOnThisNode._cachedContent = undefined;
519
+
520
+ return new RawControlledAccount(accountOnThisNode, agentSecret);
521
+ }
522
+
523
+ /** @internal */
524
+ resolveAccountAgent(
525
+ id: RawAccountID | AgentID,
526
+ expectation?: string,
527
+ ): Result<AgentID, ResolveAccountAgentError> {
528
+ if (isAgentID(id)) {
529
+ return ok(id);
542
530
  }
543
531
 
544
- /** @internal */
545
- resolveAccountAgent(
546
- id: RawAccountID | AgentID,
547
- expectation?: string,
548
- ): Result<AgentID, ResolveAccountAgentError> {
549
- if (isAgentID(id)) {
550
- return ok(id);
551
- }
532
+ let coValue: CoValueCore;
533
+
534
+ try {
535
+ coValue = this.expectCoValueLoaded(id, expectation);
536
+ } catch (e) {
537
+ return err({
538
+ type: "ErrorLoadingCoValueCore",
539
+ expectation,
540
+ id,
541
+ error: e,
542
+ } satisfies LoadCoValueCoreError);
543
+ }
552
544
 
553
- let coValue: CoValueCore;
554
-
555
- try {
556
- coValue = this.expectCoValueLoaded(id, expectation);
557
- } catch (e) {
558
- return err({
559
- type: "ErrorLoadingCoValueCore",
560
- expectation,
561
- id,
562
- error: e,
563
- } satisfies LoadCoValueCoreError);
564
- }
545
+ if (
546
+ coValue.header.type !== "comap" ||
547
+ coValue.header.ruleset.type !== "group" ||
548
+ !coValue.header.meta ||
549
+ !("type" in coValue.header.meta) ||
550
+ coValue.header.meta.type !== "account"
551
+ ) {
552
+ return err({
553
+ type: "UnexpectedlyNotAccount",
554
+ expectation,
555
+ id,
556
+ } satisfies UnexpectedlyNotAccountError);
557
+ }
565
558
 
566
- if (
567
- coValue.header.type !== "comap" ||
568
- coValue.header.ruleset.type !== "group" ||
569
- !coValue.header.meta ||
570
- !("type" in coValue.header.meta) ||
571
- coValue.header.meta.type !== "account"
572
- ) {
573
- return err({
574
- type: "UnexpectedlyNotAccount",
575
- expectation,
576
- id,
577
- } satisfies UnexpectedlyNotAccountError);
578
- }
559
+ return (coValue.getCurrentContent() as RawAccount).currentAgentID();
560
+ }
579
561
 
580
- return (coValue.getCurrentContent() as RawAccount).currentAgentID();
562
+ resolveAccountAgentAsync(
563
+ id: RawAccountID | AgentID,
564
+ expectation?: string,
565
+ ): ResultAsync<AgentID, ResolveAccountAgentError> {
566
+ if (isAgentID(id)) {
567
+ return okAsync(id);
581
568
  }
582
569
 
583
- resolveAccountAgentAsync(
584
- id: RawAccountID | AgentID,
585
- expectation?: string,
586
- ): ResultAsync<AgentID, ResolveAccountAgentError> {
587
- if (isAgentID(id)) {
588
- return okAsync(id);
570
+ return ResultAsync.fromPromise(
571
+ this.loadCoValueCore(id),
572
+ (e) =>
573
+ ({
574
+ type: "ErrorLoadingCoValueCore",
575
+ expectation,
576
+ id,
577
+ error: e,
578
+ }) satisfies LoadCoValueCoreError,
579
+ ).andThen((coValue) => {
580
+ if (coValue === "unavailable") {
581
+ return err({
582
+ type: "AccountUnavailableFromAllPeers" as const,
583
+ expectation,
584
+ id,
585
+ } satisfies AccountUnavailableFromAllPeersError);
586
+ }
587
+
588
+ if (
589
+ coValue.header.type !== "comap" ||
590
+ coValue.header.ruleset.type !== "group" ||
591
+ !coValue.header.meta ||
592
+ !("type" in coValue.header.meta) ||
593
+ coValue.header.meta.type !== "account"
594
+ ) {
595
+ return err({
596
+ type: "UnexpectedlyNotAccount" as const,
597
+ expectation,
598
+ id,
599
+ } satisfies UnexpectedlyNotAccountError);
600
+ }
601
+
602
+ return (coValue.getCurrentContent() as RawAccount).currentAgentID();
603
+ });
604
+ }
605
+
606
+ /**
607
+ * @deprecated use Account.createGroup() instead
608
+ */
609
+ createGroup(
610
+ uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
611
+ ): RawGroup {
612
+ const groupCoValue = this.createCoValue({
613
+ type: "comap",
614
+ ruleset: { type: "group", initialAdmin: this.account.id },
615
+ meta: null,
616
+ ...uniqueness,
617
+ });
618
+
619
+ const group = expectGroup(groupCoValue.getCurrentContent());
620
+
621
+ group.set(this.account.id, "admin", "trusting");
622
+
623
+ const readKey = this.crypto.newRandomKeySecret();
624
+
625
+ group.set(
626
+ `${readKey.id}_for_${this.account.id}`,
627
+ this.crypto.seal({
628
+ message: readKey.secret,
629
+ from: this.account.currentSealerSecret(),
630
+ to: this.account
631
+ .currentSealerID()
632
+ ._unsafeUnwrap({ withStackTrace: true }),
633
+ nOnceMaterial: {
634
+ in: groupCoValue.id,
635
+ tx: groupCoValue.nextTransactionID(),
636
+ },
637
+ }),
638
+ "trusting",
639
+ );
640
+
641
+ group.set("readKey", readKey.id, "trusting");
642
+
643
+ return group;
644
+ }
645
+
646
+ /** @internal */
647
+ testWithDifferentAccount(
648
+ account: ControlledAccountOrAgent,
649
+ currentSessionID: SessionID,
650
+ ): LocalNode {
651
+ const newNode = new LocalNode(account, currentSessionID, this.crypto);
652
+
653
+ const coValuesToCopy = Object.entries(this.coValues);
654
+
655
+ while (coValuesToCopy.length > 0) {
656
+ const [coValueID, entry] = coValuesToCopy[coValuesToCopy.length - 1]!;
657
+
658
+ if (entry.state.type === "unknown") {
659
+ coValuesToCopy.pop();
660
+ continue;
661
+ } else {
662
+ const allDepsCopied = entry.state.coValue
663
+ .getDependedOnCoValues()
664
+ .every((dep) => newNode.coValues[dep]?.state.type === "available");
665
+
666
+ if (!allDepsCopied) {
667
+ // move to end of queue
668
+ coValuesToCopy.unshift(coValuesToCopy.pop()!);
669
+ continue;
589
670
  }
590
671
 
591
- return ResultAsync.fromPromise(
592
- this.loadCoValueCore(id),
593
- (e) =>
594
- ({
595
- type: "ErrorLoadingCoValueCore",
596
- expectation,
597
- id,
598
- error: e,
599
- }) satisfies LoadCoValueCoreError,
600
- ).andThen((coValue) => {
601
- if (coValue === "unavailable") {
602
- return err({
603
- type: "AccountUnavailableFromAllPeers" as const,
604
- expectation,
605
- id,
606
- } satisfies AccountUnavailableFromAllPeersError);
607
- }
608
-
609
- if (
610
- coValue.header.type !== "comap" ||
611
- coValue.header.ruleset.type !== "group" ||
612
- !coValue.header.meta ||
613
- !("type" in coValue.header.meta) ||
614
- coValue.header.meta.type !== "account"
615
- ) {
616
- return err({
617
- type: "UnexpectedlyNotAccount" as const,
618
- expectation,
619
- id,
620
- } satisfies UnexpectedlyNotAccountError);
621
- }
622
-
623
- return (coValue.getCurrentContent() as RawAccount).currentAgentID();
624
- });
625
- }
626
-
627
- /**
628
- * @deprecated use Account.createGroup() instead
629
- */
630
- createGroup(
631
- uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
632
- ): RawGroup {
633
- const groupCoValue = this.createCoValue({
634
- type: "comap",
635
- ruleset: { type: "group", initialAdmin: this.account.id },
636
- meta: null,
637
- ...uniqueness,
638
- });
639
-
640
- const group = expectGroup(groupCoValue.getCurrentContent());
641
-
642
- group.set(this.account.id, "admin", "trusting");
643
-
644
- const readKey = this.crypto.newRandomKeySecret();
645
-
646
- group.set(
647
- `${readKey.id}_for_${this.account.id}`,
648
- this.crypto.seal({
649
- message: readKey.secret,
650
- from: this.account.currentSealerSecret(),
651
- to: this.account
652
- .currentSealerID()
653
- ._unsafeUnwrap({ withStackTrace: true }),
654
- nOnceMaterial: {
655
- in: groupCoValue.id,
656
- tx: groupCoValue.nextTransactionID(),
657
- },
658
- }),
659
- "trusting",
672
+ const newCoValue = new CoValueCore(
673
+ entry.state.coValue.header,
674
+ newNode,
675
+ new Map(entry.state.coValue.sessionLogs),
660
676
  );
661
677
 
662
- group.set("readKey", readKey.id, "trusting");
678
+ newNode.coValues[coValueID as RawCoID] =
679
+ CoValueState.Available(newCoValue);
663
680
 
664
- return group;
681
+ coValuesToCopy.pop();
682
+ }
665
683
  }
666
684
 
667
- /** @internal */
668
- testWithDifferentAccount(
669
- account: ControlledAccountOrAgent,
670
- currentSessionID: SessionID,
671
- ): LocalNode {
672
- const newNode = new LocalNode(account, currentSessionID, this.crypto);
673
-
674
- const coValuesToCopy = Object.entries(this.coValues);
675
-
676
- while (coValuesToCopy.length > 0) {
677
- const [coValueID, entry] =
678
- coValuesToCopy[coValuesToCopy.length - 1]!;
679
-
680
- if (entry.state.type === "unknown") {
681
- coValuesToCopy.pop();
682
- continue;
683
- } else {
684
- const allDepsCopied = entry.state.coValue
685
- .getDependedOnCoValues()
686
- .every(
687
- (dep) =>
688
- newNode.coValues[dep]?.state.type === "available",
689
- );
690
-
691
- if (!allDepsCopied) {
692
- // move to end of queue
693
- coValuesToCopy.unshift(coValuesToCopy.pop()!);
694
- continue;
695
- }
696
-
697
- const newCoValue = new CoValueCore(
698
- entry.state.coValue.header,
699
- newNode,
700
- new Map(entry.state.coValue.sessionLogs),
701
- );
702
-
703
- newNode.coValues[coValueID as RawCoID] =
704
- CoValueState.Available(newCoValue);
705
-
706
- coValuesToCopy.pop();
707
- }
708
- }
709
-
710
- if (account instanceof RawControlledAccount) {
711
- // To make sure that when we edit the account, we're modifying the correct sessions
712
- const accountInNode = new RawControlledAccount(
713
- newNode.expectCoValueLoaded(account.id),
714
- account.agentSecret,
715
- );
716
- if (accountInNode.core.node !== newNode) {
717
- throw new Error("Account's node is not the new node");
718
- }
719
- newNode.account = accountInNode;
720
- }
721
-
722
- return newNode;
685
+ if (account instanceof RawControlledAccount) {
686
+ // To make sure that when we edit the account, we're modifying the correct sessions
687
+ const accountInNode = new RawControlledAccount(
688
+ newNode.expectCoValueLoaded(account.id),
689
+ account.agentSecret,
690
+ );
691
+ if (accountInNode.core.node !== newNode) {
692
+ throw new Error("Account's node is not the new node");
693
+ }
694
+ newNode.account = accountInNode;
723
695
  }
724
696
 
725
- gracefulShutdown() {
726
- this.syncManager.gracefulShutdown();
727
- }
697
+ return newNode;
698
+ }
699
+
700
+ gracefulShutdown() {
701
+ this.syncManager.gracefulShutdown();
702
+ }
728
703
  }
729
704
 
730
705
  export type LoadCoValueCoreError = {
731
- type: "ErrorLoadingCoValueCore";
732
- error: unknown;
733
- expectation?: string;
734
- id: RawAccountID;
706
+ type: "ErrorLoadingCoValueCore";
707
+ error: unknown;
708
+ expectation?: string;
709
+ id: RawAccountID;
735
710
  };
736
711
 
737
712
  export type AccountUnavailableFromAllPeersError = {
738
- type: "AccountUnavailableFromAllPeers";
739
- expectation?: string;
740
- id: RawAccountID;
713
+ type: "AccountUnavailableFromAllPeers";
714
+ expectation?: string;
715
+ id: RawAccountID;
741
716
  };
742
717
 
743
718
  export type UnexpectedlyNotAccountError = {
744
- type: "UnexpectedlyNotAccount";
745
- expectation?: string;
746
- id: RawAccountID;
719
+ type: "UnexpectedlyNotAccount";
720
+ expectation?: string;
721
+ id: RawAccountID;
747
722
  };
748
723
 
749
724
  export type ResolveAccountAgentError =
750
- | InvalidAccountAgentIDError
751
- | LoadCoValueCoreError
752
- | AccountUnavailableFromAllPeersError
753
- | UnexpectedlyNotAccountError;
725
+ | InvalidAccountAgentIDError
726
+ | LoadCoValueCoreError
727
+ | AccountUnavailableFromAllPeersError
728
+ | UnexpectedlyNotAccountError;