cojson 0.2.3 → 0.3.0

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 (78) hide show
  1. package/dist/account.d.ts +1 -1
  2. package/dist/coValue.d.ts +5 -13
  3. package/dist/coValue.js +14 -7
  4. package/dist/coValue.js.map +1 -1
  5. package/dist/coValueCore.d.ts +6 -6
  6. package/dist/coValueCore.js +11 -14
  7. package/dist/coValueCore.js.map +1 -1
  8. package/dist/coValues/coList.d.ts +99 -34
  9. package/dist/coValues/coList.js +162 -72
  10. package/dist/coValues/coList.js.map +1 -1
  11. package/dist/coValues/coMap.d.ts +96 -31
  12. package/dist/coValues/coMap.js +157 -114
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.d.ts +67 -23
  15. package/dist/coValues/coStream.js +131 -59
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/crypto.d.ts +8 -3
  18. package/dist/crypto.js +6 -6
  19. package/dist/crypto.js.map +1 -1
  20. package/dist/group.d.ts +57 -23
  21. package/dist/group.js +75 -33
  22. package/dist/group.js.map +1 -1
  23. package/dist/index.d.ts +8 -6
  24. package/dist/index.js +8 -8
  25. package/dist/index.js.map +1 -1
  26. package/dist/{node.d.ts → localNode.d.ts} +16 -8
  27. package/dist/{node.js → localNode.js} +48 -40
  28. package/dist/localNode.js.map +1 -0
  29. package/dist/permissions.js +6 -3
  30. package/dist/permissions.js.map +1 -1
  31. package/dist/queriedCoValues/queriedCoList.d.ts +66 -0
  32. package/dist/queriedCoValues/queriedCoList.js +120 -0
  33. package/dist/queriedCoValues/queriedCoList.js.map +1 -0
  34. package/dist/queriedCoValues/queriedCoMap.d.ts +47 -0
  35. package/dist/queriedCoValues/queriedCoMap.js +83 -0
  36. package/dist/queriedCoValues/queriedCoMap.js.map +1 -0
  37. package/dist/queriedCoValues/queriedCoStream.d.ts +40 -0
  38. package/dist/queriedCoValues/queriedCoStream.js +72 -0
  39. package/dist/queriedCoValues/queriedCoStream.js.map +1 -0
  40. package/dist/queries.d.ts +29 -112
  41. package/dist/queries.js +44 -227
  42. package/dist/queries.js.map +1 -1
  43. package/dist/sync.d.ts +1 -1
  44. package/dist/sync.js +1 -1
  45. package/dist/sync.js.map +1 -1
  46. package/dist/tests/testUtils.d.ts +1 -1
  47. package/dist/tests/testUtils.js +3 -3
  48. package/dist/tests/testUtils.js.map +1 -1
  49. package/package.json +2 -2
  50. package/src/account.ts +1 -1
  51. package/src/coValue.ts +25 -20
  52. package/src/coValueCore.ts +17 -21
  53. package/src/coValues/coList.ts +242 -128
  54. package/src/coValues/coMap.ts +293 -162
  55. package/src/coValues/coStream.ts +227 -94
  56. package/src/crypto.ts +37 -24
  57. package/src/group.ts +90 -63
  58. package/src/index.ts +35 -25
  59. package/src/{node.ts → localNode.ts} +64 -64
  60. package/src/permissions.ts +15 -18
  61. package/src/queriedCoValues/queriedCoList.ts +248 -0
  62. package/src/queriedCoValues/queriedCoMap.ts +180 -0
  63. package/src/queriedCoValues/queriedCoStream.ts +125 -0
  64. package/src/queries.ts +83 -460
  65. package/src/sync.ts +2 -2
  66. package/src/tests/account.test.ts +3 -6
  67. package/src/tests/coValue.test.ts +116 -110
  68. package/src/tests/coValueCore.test.ts +1 -1
  69. package/src/tests/crypto.test.ts +19 -21
  70. package/src/tests/permissions.test.ts +255 -242
  71. package/src/tests/queries.test.ts +57 -40
  72. package/src/tests/sync.test.ts +30 -30
  73. package/src/tests/testUtils.ts +3 -3
  74. package/dist/coValues/static.d.ts +0 -14
  75. package/dist/coValues/static.js +0 -20
  76. package/dist/coValues/static.js.map +0 -1
  77. package/dist/node.js.map +0 -1
  78. package/src/coValues/static.ts +0 -31
package/src/group.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CoID, CoValue, AnyCoValue } from "./coValue.js";
1
+ import { CoID, CoValue, AnyCoValue, AnyCoMap, AnyCoList } from "./coValue.js";
2
2
  import { CoMap } from "./coValues/coMap.js";
3
3
  import { JsonObject, JsonValue } from "./jsonValue.js";
4
4
  import {
@@ -15,12 +15,11 @@ import {
15
15
  agentSecretFromSecretSeed,
16
16
  getAgentID,
17
17
  } from "./crypto.js";
18
- import { LocalNode } from "./node.js";
18
+ import { LocalNode } from "./localNode.js";
19
19
  import { AgentID, SessionID, isAgentID } from "./ids.js";
20
20
  import { AccountID, GeneralizedControlledAccount, Profile } from "./account.js";
21
21
  import { Role } from "./permissions.js";
22
22
  import { base58 } from "@scure/base";
23
- import { CoList } from "./coValues/coList.js";
24
23
  import {
25
24
  BinaryCoStream,
26
25
  BinaryCoStreamMeta,
@@ -28,9 +27,9 @@ import {
28
27
  } from "./coValues/coStream.js";
29
28
 
30
29
  export type GroupContent = {
31
- profile: CoID<Profile> | null;
30
+ profile?: CoID<Profile> | null;
32
31
  [key: AccountID | AgentID]: Role;
33
- readKey: KeyID;
32
+ readKey?: KeyID;
34
33
  [revelationFor: `${KeyID}_for_${AccountID | AgentID}`]: Sealed<KeySecret>;
35
34
  [oldKeyForNewKey: `${KeyID}_for_${KeyID}`]: Encrypted<
36
35
  KeySecret,
@@ -70,6 +69,7 @@ export function expectGroupContent(
70
69
  * ```
71
70
  * */
72
71
  export class Group {
72
+ /** @category 4. Underlying CoMap */
73
73
  underlyingMap: CoMap<GroupContent, JsonObject | null>;
74
74
  /** @internal */
75
75
  node: LocalNode;
@@ -83,12 +83,20 @@ export class Group {
83
83
  this.node = node;
84
84
  }
85
85
 
86
- /** Returns the `CoID` of the `Group`. */
86
+ /**
87
+ * Returns the `CoID` of the `Group`.
88
+ *
89
+ * @category 4. Underlying CoMap
90
+ */
87
91
  get id(): CoID<CoMap<GroupContent, JsonObject | null>> {
88
92
  return this.underlyingMap.id;
89
93
  }
90
94
 
91
- /** Returns the current role of a given account. */
95
+ /**
96
+ * Returns the current role of a given account.
97
+ *
98
+ * @category 1. Role reading
99
+ */
92
100
  roleOf(accountID: AccountID): Role | undefined {
93
101
  return this.roleOfInternal(accountID);
94
102
  }
@@ -98,20 +106,28 @@ export class Group {
98
106
  return this.underlyingMap.get(accountID);
99
107
  }
100
108
 
101
- /** Returns the role of the current account in the group. */
109
+ /**
110
+ * Returns the role of the current account in the group.
111
+ *
112
+ * @category 1. Role reading
113
+ */
102
114
  myRole(): Role | undefined {
103
115
  return this.roleOfInternal(this.node.account.id);
104
116
  }
105
117
 
106
- /** Directly grants a new member a role in the group. The current account must be an
107
- * admin to be able to do so. Throws otherwise. */
118
+ /**
119
+ * Directly grants a new member a role in the group. The current account must be an
120
+ * admin to be able to do so. Throws otherwise.
121
+ *
122
+ * @category 2. Role changing
123
+ */
108
124
  addMember(accountID: AccountID, role: Role) {
109
125
  this.addMemberInternal(accountID, role);
110
126
  }
111
127
 
112
128
  /** @internal */
113
129
  addMemberInternal(accountID: AccountID | AgentID, role: Role) {
114
- this.underlyingMap = this.underlyingMap.edit((map) => {
130
+ this.underlyingMap = this.underlyingMap.mutate((map) => {
115
131
  const currentReadKey = this.underlyingMap.core.getCurrentReadKey();
116
132
 
117
133
  if (!currentReadKey.secret) {
@@ -131,15 +147,15 @@ export class Group {
131
147
 
132
148
  map.set(
133
149
  `${currentReadKey.id}_for_${accountID}`,
134
- seal(
135
- currentReadKey.secret,
136
- this.underlyingMap.core.node.account.currentSealerSecret(),
137
- getAgentSealerID(agent),
138
- {
150
+ seal({
151
+ message: currentReadKey.secret,
152
+ from: this.underlyingMap.core.node.account.currentSealerSecret(),
153
+ to: getAgentSealerID(agent),
154
+ nOnceMaterial: {
139
155
  in: this.underlyingMap.core.id,
140
156
  tx: this.underlyingMap.core.nextTransactionID(),
141
- }
142
- ),
157
+ },
158
+ }),
143
159
  "trusting"
144
160
  );
145
161
  });
@@ -177,7 +193,7 @@ export class Group {
177
193
 
178
194
  const newReadKey = newRandomKeySecret();
179
195
 
180
- this.underlyingMap = this.underlyingMap.edit((map) => {
196
+ this.underlyingMap = this.underlyingMap.mutate((map) => {
181
197
  for (const readerID of currentlyPermittedReaders) {
182
198
  const reader = this.node.resolveAccountAgent(
183
199
  readerID,
@@ -186,15 +202,15 @@ export class Group {
186
202
 
187
203
  map.set(
188
204
  `${newReadKey.id}_for_${readerID}`,
189
- seal(
190
- newReadKey.secret,
191
- this.underlyingMap.core.node.account.currentSealerSecret(),
192
- getAgentSealerID(reader),
193
- {
205
+ seal({
206
+ message: newReadKey.secret,
207
+ from: this.underlyingMap.core.node.account.currentSealerSecret(),
208
+ to: getAgentSealerID(reader),
209
+ nOnceMaterial: {
194
210
  in: this.underlyingMap.core.id,
195
211
  tx: this.underlyingMap.core.nextTransactionID(),
196
- }
197
- ),
212
+ },
213
+ }),
198
214
  "trusting"
199
215
  );
200
216
  }
@@ -212,23 +228,33 @@ export class Group {
212
228
  });
213
229
  }
214
230
 
215
- /** Strips the specified member of all roles (preventing future writes in
231
+ /**
232
+ * Strips the specified member of all roles (preventing future writes in
216
233
  * the group and owned values) and rotates the read encryption key for that group
217
- * (preventing reads of new content in the group and owned values) */
234
+ * (preventing reads of new content in the group and owned values)
235
+ *
236
+ * @category 2. Role changing
237
+ */
218
238
  removeMember(accountID: AccountID) {
219
239
  this.removeMemberInternal(accountID);
220
240
  }
221
241
 
222
242
  /** @internal */
223
243
  removeMemberInternal(accountID: AccountID | AgentID) {
224
- this.underlyingMap = this.underlyingMap.edit((map) => {
244
+ this.underlyingMap = this.underlyingMap.mutate((map) => {
225
245
  map.set(accountID, "revoked", "trusting");
226
246
  });
227
247
 
228
248
  this.rotateReadKey();
229
249
  }
230
250
 
231
- /** Creates an invite for new members to indirectly join the group, allowing them to grant themselves the specified role with the InviteSecret (a string starting with "inviteSecret_") - use `LocalNode.acceptInvite()` for this purpose. */
251
+ /**
252
+ * Creates an invite for new members to indirectly join the group,
253
+ * allowing them to grant themselves the specified role with the InviteSecret
254
+ * (a string starting with "inviteSecret_") - use `LocalNode.acceptInvite()` for this purpose.
255
+ *
256
+ * @category 2. Role changing
257
+ */
232
258
  createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
233
259
  const secretSeed = newRandomSecretSeed();
234
260
 
@@ -240,22 +266,20 @@ export class Group {
240
266
  return inviteSecretFromSecretSeed(secretSeed);
241
267
  }
242
268
 
243
- /** Creates a new `CoMap` within this group, with the specified specialized
244
- * `CoMap` type `M` and optional static metadata. */
245
- createMap<
246
- M extends CoMap<
247
- { [key: string]: JsonValue | AnyCoValue | undefined },
248
- JsonObject | null
249
- >
250
- >(
251
- init?: M extends CoMap<infer M, infer _Meta>
252
- ? {
253
- [K in keyof M]: M[K] extends AnyCoValue
254
- ? M[K] | CoID<M[K]>
255
- : M[K];
256
- }
257
- : never,
258
- meta?: M["meta"]
269
+ /**
270
+ * Creates a new `CoMap` within this group, with the specified specialized
271
+ * `CoMap` type `M` and optional static metadata.
272
+ *
273
+ * @category 3. Value creation
274
+ */
275
+ createMap<M extends AnyCoMap>(
276
+ init?: {
277
+ [K in keyof M["_shape"]]: M["_shape"][K] extends AnyCoValue
278
+ ? M["_shape"][K] | CoID<M["_shape"][K]>
279
+ : M["_shape"][K];
280
+ },
281
+ meta?: M["meta"],
282
+ initPrivacy: "trusting" | "private" = "trusting"
259
283
  ): M {
260
284
  let map = this.node
261
285
  .createCoValue({
@@ -270,23 +294,26 @@ export class Group {
270
294
  .getCurrentContent() as M;
271
295
 
272
296
  if (init) {
273
- map = map.edit((editable) => {
274
- for (const [key, value] of Object.entries(init)) {
275
- editable.set(key, value);
276
- }
277
- });
297
+ for (const [key, value] of Object.entries(init)) {
298
+ map = map.set(key, value, initPrivacy);
299
+ }
278
300
  }
279
301
 
280
302
  return map;
281
303
  }
282
304
 
283
- /** Creates a new `CoList` within this group, with the specified specialized
284
- * `CoList` type `L` and optional static metadata. */
285
- createList<L extends CoList<JsonValue | CoValue, JsonObject | null>>(
286
- init?: L extends CoList<infer I, infer _Meta>
287
- ? (I extends CoValue ? CoID<I> | I : I)[]
288
- : never,
289
- meta?: L["meta"]
305
+ /**
306
+ * Creates a new `CoList` within this group, with the specified specialized
307
+ * `CoList` type `L` and optional static metadata.
308
+ *
309
+ * @category 3. Value creation
310
+ */
311
+ createList<L extends AnyCoList>(
312
+ init?: (L["_item"] extends CoValue
313
+ ? CoID<L["_item"]> | L["_item"]
314
+ : L["_item"])[],
315
+ meta?: L["meta"],
316
+ initPrivacy: "trusting" | "private" = "trusting"
290
317
  ): L {
291
318
  let list = this.node
292
319
  .createCoValue({
@@ -301,16 +328,15 @@ export class Group {
301
328
  .getCurrentContent() as L;
302
329
 
303
330
  if (init) {
304
- list = list.edit((editable) => {
305
- for (const item of init) {
306
- editable.push(item);
307
- }
308
- });
331
+ for (const item of init) {
332
+ list = list.append(item, undefined, initPrivacy);
333
+ }
309
334
  }
310
335
 
311
336
  return list;
312
337
  }
313
338
 
339
+ /** @category 3. Value creation */
314
340
  createStream<C extends CoStream<JsonValue | CoValue, JsonObject | null>>(
315
341
  meta?: C["meta"]
316
342
  ): C {
@@ -327,6 +353,7 @@ export class Group {
327
353
  .getCurrentContent() as C;
328
354
  }
329
355
 
356
+ /** @category 3. Value creation */
330
357
  createBinaryStream<C extends BinaryCoStream<BinaryCoStreamMeta>>(
331
358
  meta: C["meta"] = { type: "binary" }
332
359
  ): C {
package/src/index.ts CHANGED
@@ -1,13 +1,17 @@
1
- import { CoValueCore, newRandomSessionID, MAX_RECOMMENDED_TX_SIZE } from "./coValueCore.js";
2
- import { LocalNode } from "./node.js";
1
+ import {
2
+ CoValueCore,
3
+ newRandomSessionID,
4
+ MAX_RECOMMENDED_TX_SIZE,
5
+ } from "./coValueCore.js";
6
+ import { LocalNode } from "./localNode.js";
3
7
  import type { CoValue } from "./coValue.js";
4
- import { CoMap, WriteableCoMap } from "./coValues/coMap.js";
5
- import { CoList, WriteableCoList } from "./coValues/coList.js";
8
+ import { CoMap, MutableCoMap } from "./coValues/coMap.js";
9
+ import { CoList, MutableCoList } from "./coValues/coList.js";
6
10
  import {
7
11
  CoStream,
8
- WriteableCoStream,
12
+ MutableCoStream,
9
13
  BinaryCoStream,
10
- WriteableBinaryCoStream,
14
+ MutableBinaryCoStream,
11
15
  } from "./coValues/coStream.js";
12
16
  import {
13
17
  agentSecretFromBytes,
@@ -18,7 +22,7 @@ import {
18
22
  agentSecretFromSecretSeed,
19
23
  secretSeedLength,
20
24
  shortHashLength,
21
- cryptoReady
25
+ cryptoReady,
22
26
  } from "./crypto.js";
23
27
  import { connectedPeers } from "./streamUtils.js";
24
28
  import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
@@ -30,7 +34,13 @@ import { parseJSON } from "./jsonStringify.js";
30
34
  import type { SessionID, AgentID } from "./ids.js";
31
35
  import type { CoID, AnyCoValue } from "./coValue.js";
32
36
  import type { Queried } from "./queries.js";
33
- import type { BinaryStreamInfo, BinaryCoStreamMeta } from "./coValues/coStream.js";
37
+ import type { QueriedCoStream } from "./queriedCoValues/queriedCoStream.js";
38
+ import type { QueriedCoList } from "./queriedCoValues/queriedCoList.js";
39
+ import type { QueriedCoMap } from "./queriedCoValues/queriedCoMap.js";
40
+ import type {
41
+ BinaryStreamInfo,
42
+ BinaryCoStreamMeta,
43
+ } from "./coValues/coStream.js";
34
44
  import type { JsonValue } from "./jsonValue.js";
35
45
  import type { SyncMessage, Peer } from "./sync.js";
36
46
  import type { AgentSecret } from "./crypto.js";
@@ -57,38 +67,39 @@ export const cojsonInternals = {
57
67
  expectGroupContent,
58
68
  base64URLtoBytes,
59
69
  bytesToBase64url,
60
- parseJSON
70
+ parseJSON,
61
71
  };
62
72
 
63
73
  export {
64
74
  LocalNode,
65
75
  Group,
66
76
  CoMap,
67
- WriteableCoMap,
77
+ MutableCoMap,
68
78
  CoList,
69
- WriteableCoList,
79
+ MutableCoList,
70
80
  CoStream,
71
- WriteableCoStream,
81
+ MutableCoStream,
72
82
  BinaryCoStream,
73
- WriteableBinaryCoStream,
74
- CoValueCore,
75
- AnonymousControlledAccount,
76
- ControlledAccount,
77
- cryptoReady as cojsonReady,
78
- MAX_RECOMMENDED_TX_SIZE
79
- };
80
-
81
- export type {
82
- Value,
83
- JsonValue,
83
+ MutableBinaryCoStream,
84
84
  CoValue,
85
- AnyCoValue,
86
85
  CoID,
86
+ AnyCoValue,
87
87
  Queried,
88
+ QueriedCoMap,
89
+ QueriedCoList,
90
+ QueriedCoStream,
88
91
  AccountID,
89
92
  Account,
90
93
  Profile,
91
94
  SessionID,
95
+ Media,
96
+ CoValueCore,
97
+ AnonymousControlledAccount,
98
+ ControlledAccount,
99
+ cryptoReady as cojsonReady,
100
+ MAX_RECOMMENDED_TX_SIZE,
101
+ Value,
102
+ JsonValue,
92
103
  Peer,
93
104
  BinaryStreamInfo,
94
105
  BinaryCoStreamMeta,
@@ -96,7 +107,6 @@ export type {
96
107
  AgentSecret,
97
108
  InviteSecret,
98
109
  SyncMessage,
99
- Media
100
110
  };
101
111
 
102
112
  // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -35,7 +35,6 @@ import {
35
35
  AccountID,
36
36
  Profile,
37
37
  AccountContent,
38
- Account,
39
38
  } from "./account.js";
40
39
  import { CoMap } from "./coValues/coMap.js";
41
40
  import { CoValue } from "./index.js";
@@ -54,11 +53,14 @@ const { localNode } = useJazz();
54
53
  export class LocalNode {
55
54
  /** @internal */
56
55
  coValues: { [key: RawCoID]: CoValueState } = {};
57
- /** @internal */
56
+ /** @category 3. Low-level */
58
57
  account: GeneralizedControlledAccount;
58
+ /** @category 3. Low-level */
59
59
  currentSessionID: SessionID;
60
- sync = new SyncManager(this);
60
+ /** @category 3. Low-level */
61
+ syncManager = new SyncManager(this);
61
62
 
63
+ /** @category 3. Low-level */
62
64
  constructor(
63
65
  account: GeneralizedControlledAccount,
64
66
  currentSessionID: SessionID
@@ -67,6 +69,7 @@ export class LocalNode {
67
69
  this.currentSessionID = currentSessionID;
68
70
  }
69
71
 
72
+ /** @category 2. Node Creation */
70
73
  static withNewlyCreatedAccount(
71
74
  name: string,
72
75
  initialAgentSecret = newRandomAgentSecret()
@@ -97,6 +100,7 @@ export class LocalNode {
97
100
  };
98
101
  }
99
102
 
103
+ /** @category 2. Node Creation */
100
104
  static async withLoadedAccount(
101
105
  accountID: AccountID,
102
106
  accountSecret: AgentSecret,
@@ -111,7 +115,7 @@ export class LocalNode {
111
115
  const accountPromise = loadingNode.load(accountID);
112
116
 
113
117
  for (const peer of peersToLoadFrom) {
114
- loadingNode.sync.addPeer(peer);
118
+ loadingNode.syncManager.addPeer(peer);
115
119
  }
116
120
 
117
121
  const account = await accountPromise;
@@ -121,8 +125,8 @@ export class LocalNode {
121
125
  new ControlledAccount(accountSecret, account, loadingNode),
122
126
  sessionID
123
127
  );
124
- node.sync = loadingNode.sync;
125
- node.sync.local = node;
128
+ node.syncManager = loadingNode.syncManager;
129
+ node.syncManager.local = node;
126
130
 
127
131
  return node;
128
132
  }
@@ -132,7 +136,7 @@ export class LocalNode {
132
136
  const coValue = new CoValueCore(header, this);
133
137
  this.coValues[coValue.id] = { state: "loaded", coValue: coValue };
134
138
 
135
- void this.sync.syncCoValue(coValue);
139
+ void this.syncManager.syncCoValue(coValue);
136
140
 
137
141
  return coValue;
138
142
  }
@@ -145,7 +149,7 @@ export class LocalNode {
145
149
 
146
150
  this.coValues[id] = entry;
147
151
 
148
- this.sync.loadFromPeers(id);
152
+ this.syncManager.loadFromPeers(id);
149
153
  }
150
154
  if (entry.state === "loaded") {
151
155
  return Promise.resolve(entry.coValue);
@@ -157,33 +161,42 @@ export class LocalNode {
157
161
  * Loads a CoValue's content, syncing from peers as necessary and resolving the returned
158
162
  * promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
159
163
  * for listening to subsequent updates to the CoValue.
164
+ *
165
+ * @category 3. Low-level
160
166
  */
161
167
  async load<T extends CoValue>(id: CoID<T>): Promise<T> {
162
168
  return (await this.loadCoValue(id)).getCurrentContent() as T;
163
169
  }
164
170
 
165
- subscribe<T extends CoValue>(id: CoID<T>, callback: (update: T) => void): () => void {
171
+ /** @category 3. Low-level */
172
+ subscribe<T extends CoValue>(
173
+ id: CoID<T>,
174
+ callback: (update: T) => void
175
+ ): () => void {
166
176
  let stopped = false;
167
177
  let unsubscribe!: () => void;
168
178
 
169
179
  console.log("Subscribing to " + id);
170
180
 
171
- this.load(id).then((coValue) => {
172
- if (stopped) {
173
- return;
174
- }
175
- unsubscribe = coValue.subscribe(callback);
176
- }).catch((e) => {
177
- console.error("Error subscribing to ", id, e);
178
- });
181
+ this.load(id)
182
+ .then((coValue) => {
183
+ if (stopped) {
184
+ return;
185
+ }
186
+ unsubscribe = coValue.subscribe(callback);
187
+ })
188
+ .catch((e) => {
189
+ console.error("Error subscribing to ", id, e);
190
+ });
179
191
 
180
192
  return () => {
181
193
  console.log("Unsubscribing from " + id);
182
194
  stopped = true;
183
195
  unsubscribe?.();
184
- }
196
+ };
185
197
  }
186
198
 
199
+ /** @category 1. High-level */
187
200
  query<T extends CoValue>(
188
201
  id: CoID<T>,
189
202
  callback: (update: Queried<T> | undefined) => void
@@ -191,22 +204,7 @@ export class LocalNode {
191
204
  return query(id, this, callback);
192
205
  }
193
206
 
194
- /**
195
- * Loads a profile associated with an account. `Profile` is at least a `CoMap<{string: name}>`,
196
- * but might contain other, app-specific properties.
197
- */
198
- async loadProfile(id: AccountID): Promise<Profile> {
199
- const account = await this.load<Account>(id);
200
- const profileID = account.get("profile");
201
-
202
- if (!profileID) {
203
- throw new Error(`Account ${id} has no profile`);
204
- }
205
- return (
206
- await this.loadCoValue(profileID)
207
- ).getCurrentContent() as Profile;
208
- }
209
-
207
+ /** @category 1. High-level */
210
208
  async acceptInvite<T extends CoValue>(
211
209
  groupOrOwnedValueID: CoID<T>,
212
210
  inviteSecret: InviteSecret
@@ -327,11 +325,12 @@ export class LocalNode {
327
325
  name: string,
328
326
  agentSecret = newRandomAgentSecret()
329
327
  ): ControlledAccount {
328
+ const accountAgentID = getAgentID(agentSecret);
330
329
  const account = this.createCoValue(
331
330
  accountHeaderForInitialAgentSecret(agentSecret)
332
331
  ).testWithDifferentAccount(
333
332
  new AnonymousControlledAccount(agentSecret),
334
- newRandomSessionID(getAgentID(agentSecret))
333
+ newRandomSessionID(accountAgentID)
335
334
  );
336
335
 
337
336
  const accountAsGroup = new Group(
@@ -339,22 +338,22 @@ export class LocalNode {
339
338
  account.node
340
339
  );
341
340
 
342
- accountAsGroup.underlyingMap.edit((editable) => {
343
- editable.set(getAgentID(agentSecret), "admin", "trusting");
341
+ accountAsGroup.underlyingMap.mutate((editable) => {
342
+ editable.set(accountAgentID, "admin", "trusting");
344
343
 
345
344
  const readKey = newRandomKeySecret();
346
345
 
347
346
  editable.set(
348
- `${readKey.id}_for_${getAgentID(agentSecret)}`,
349
- seal(
350
- readKey.secret,
351
- getAgentSealerSecret(agentSecret),
352
- getAgentSealerID(getAgentID(agentSecret)),
353
- {
347
+ `${readKey.id}_for_${accountAgentID}`,
348
+ seal({
349
+ message: readKey.secret,
350
+ from: getAgentSealerSecret(agentSecret),
351
+ to: getAgentSealerID(accountAgentID),
352
+ nOnceMaterial: {
354
353
  in: account.id,
355
354
  tx: account.nextTransactionID(),
356
- }
357
- ),
355
+ },
356
+ }),
358
357
  "trusting"
359
358
  );
360
359
 
@@ -367,17 +366,15 @@ export class LocalNode {
367
366
  account.node
368
367
  );
369
368
 
370
- let profile = accountAsGroup.createMap<Profile>(undefined, {
371
- type: "profile",
372
- });
373
-
374
- profile = profile.edit((editable) => {
375
- editable.set("name", name, "trusting");
376
- });
369
+ const profile = accountAsGroup.createMap<Profile>(
370
+ { name },
371
+ {
372
+ type: "profile",
373
+ },
374
+ "trusting"
375
+ );
377
376
 
378
- accountAsGroup.underlyingMap.edit((editable) => {
379
- editable.set("profile", profile.id, "trusting");
380
- });
377
+ accountAsGroup.underlyingMap.set("profile", profile.id, "trusting");
381
378
 
382
379
  const accountOnThisNode = this.expectCoValueLoaded(account.id);
383
380
 
@@ -427,7 +424,10 @@ export class LocalNode {
427
424
  ).getCurrentAgentID();
428
425
  }
429
426
 
430
- /** Creates a new group (with the current account as the group's first admin). */
427
+ /**
428
+ * Creates a new group (with the current account as the group's first admin).
429
+ * @category 1. High-level
430
+ */
431
431
  createGroup(): Group {
432
432
  const groupCoValue = this.createCoValue({
433
433
  type: "comap",
@@ -438,22 +438,22 @@ export class LocalNode {
438
438
 
439
439
  let groupContent = expectGroupContent(groupCoValue.getCurrentContent());
440
440
 
441
- groupContent = groupContent.edit((editable) => {
441
+ groupContent = groupContent.mutate((editable) => {
442
442
  editable.set(this.account.id, "admin", "trusting");
443
443
 
444
444
  const readKey = newRandomKeySecret();
445
445
 
446
446
  editable.set(
447
447
  `${readKey.id}_for_${this.account.id}`,
448
- seal(
449
- readKey.secret,
450
- this.account.currentSealerSecret(),
451
- this.account.currentSealerID(),
452
- {
448
+ seal({
449
+ message: readKey.secret,
450
+ from: this.account.currentSealerSecret(),
451
+ to: this.account.currentSealerID(),
452
+ nOnceMaterial: {
453
453
  in: groupCoValue.id,
454
454
  tx: groupCoValue.nextTransactionID(),
455
- }
456
- ),
455
+ },
456
+ }),
457
457
  "trusting"
458
458
  );
459
459