cojson 0.1.8 → 0.1.10

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 (76) hide show
  1. package/dist/account.d.ts +6 -3
  2. package/dist/account.js +4 -2
  3. package/dist/account.js.map +1 -1
  4. package/dist/coValue.d.ts +44 -80
  5. package/dist/coValue.js +4 -348
  6. package/dist/coValue.js.map +1 -1
  7. package/dist/coValueCore.d.ts +84 -0
  8. package/dist/coValueCore.js +356 -0
  9. package/dist/coValueCore.js.map +1 -0
  10. package/dist/coValues/coList.d.ts +114 -0
  11. package/dist/{contentTypes → coValues}/coList.js +59 -19
  12. package/dist/coValues/coList.js.map +1 -0
  13. package/dist/{contentTypes → coValues}/coMap.d.ts +25 -7
  14. package/dist/{contentTypes → coValues}/coMap.js +34 -15
  15. package/dist/coValues/coMap.js.map +1 -0
  16. package/dist/coValues/coStream.d.ts +69 -0
  17. package/dist/coValues/coStream.js +131 -0
  18. package/dist/coValues/coStream.js.map +1 -0
  19. package/dist/coValues/static.d.ts +14 -0
  20. package/dist/coValues/static.js +20 -0
  21. package/dist/coValues/static.js.map +1 -0
  22. package/dist/group.d.ts +57 -9
  23. package/dist/group.js +94 -28
  24. package/dist/group.js.map +1 -1
  25. package/dist/index.d.ts +19 -10
  26. package/dist/index.js +7 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/node.d.ts +59 -5
  29. package/dist/node.js +36 -15
  30. package/dist/node.js.map +1 -1
  31. package/dist/permissions.d.ts +2 -2
  32. package/dist/permissions.js +1 -1
  33. package/dist/permissions.js.map +1 -1
  34. package/dist/sync.d.ts +3 -3
  35. package/dist/sync.js +2 -2
  36. package/dist/sync.js.map +1 -1
  37. package/dist/testUtils.d.ts +2 -2
  38. package/dist/testUtils.js +1 -1
  39. package/dist/testUtils.js.map +1 -1
  40. package/package.json +2 -2
  41. package/src/account.test.ts +1 -1
  42. package/src/account.ts +8 -5
  43. package/src/coValue.test.ts +335 -129
  44. package/src/coValue.ts +52 -576
  45. package/src/coValueCore.test.ts +180 -0
  46. package/src/coValueCore.ts +592 -0
  47. package/src/{contentTypes → coValues}/coList.ts +91 -42
  48. package/src/{contentTypes → coValues}/coMap.ts +40 -20
  49. package/src/coValues/coStream.ts +249 -0
  50. package/src/coValues/static.ts +31 -0
  51. package/src/group.test.ts +47 -0
  52. package/src/group.ts +120 -50
  53. package/src/index.ts +43 -28
  54. package/src/node.ts +48 -27
  55. package/src/permissions.test.ts +32 -32
  56. package/src/permissions.ts +5 -5
  57. package/src/sync.test.ts +77 -77
  58. package/src/sync.ts +5 -5
  59. package/src/testUtils.ts +1 -1
  60. package/tsconfig.json +1 -2
  61. package/dist/contentType.d.ts +0 -15
  62. package/dist/contentType.js +0 -7
  63. package/dist/contentType.js.map +0 -1
  64. package/dist/contentTypes/coList.d.ts +0 -77
  65. package/dist/contentTypes/coList.js.map +0 -1
  66. package/dist/contentTypes/coMap.js.map +0 -1
  67. package/dist/contentTypes/coStream.d.ts +0 -11
  68. package/dist/contentTypes/coStream.js +0 -16
  69. package/dist/contentTypes/coStream.js.map +0 -1
  70. package/dist/contentTypes/static.d.ts +0 -11
  71. package/dist/contentTypes/static.js +0 -14
  72. package/dist/contentTypes/static.js.map +0 -1
  73. package/src/contentType.test.ts +0 -284
  74. package/src/contentType.ts +0 -26
  75. package/src/contentTypes/coStream.ts +0 -24
  76. package/src/contentTypes/static.ts +0 -22
@@ -0,0 +1,47 @@
1
+ import { LocalNode, CoMap, CoList, CoStream, BinaryCoStream } from "./index";
2
+ import { randomAnonymousAccountAndSessionID } from "./testUtils";
3
+
4
+ test("Can create a CoMap in a group", () => {
5
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
6
+
7
+ const group = node.createGroup();
8
+
9
+ const map = group.createMap();
10
+
11
+ expect(map.core.getCurrentContent().type).toEqual("comap");
12
+ expect(map instanceof CoMap).toEqual(true);
13
+ });
14
+
15
+ test("Can create a CoList in a group", () => {
16
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
17
+
18
+ const group = node.createGroup();
19
+
20
+ const list = group.createList();
21
+
22
+ expect(list.core.getCurrentContent().type).toEqual("colist");
23
+ expect(list instanceof CoList).toEqual(true);
24
+ })
25
+
26
+ test("Can create a CoStream in a group", () => {
27
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
28
+
29
+ const group = node.createGroup();
30
+
31
+ const stream = group.createStream();
32
+
33
+ expect(stream.core.getCurrentContent().type).toEqual("costream");
34
+ expect(stream instanceof CoStream).toEqual(true);
35
+ });
36
+
37
+ test("Can create a BinaryCoStream in a group", () => {
38
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
39
+
40
+ const group = node.createGroup();
41
+
42
+ const stream = group.createBinaryStream();
43
+
44
+ expect(stream.core.getCurrentContent().type).toEqual("costream");
45
+ expect(stream.meta.type).toEqual("binary");
46
+ expect(stream instanceof BinaryCoStream).toEqual(true);
47
+ })
package/src/group.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { CoID, ContentType } from "./contentType.js";
2
- import { CoMap } from "./contentTypes/coMap.js";
1
+ import { CoID, CoValueImpl } from "./coValue.js";
2
+ import { CoMap } from "./coValues/coMap.js";
3
3
  import { JsonObject, JsonValue } from "./jsonValue.js";
4
4
  import {
5
5
  Encrypted,
@@ -17,14 +17,11 @@ import {
17
17
  } from "./crypto.js";
18
18
  import { LocalNode } from "./node.js";
19
19
  import { AgentID, SessionID, isAgentID } from "./ids.js";
20
- import {
21
- AccountID,
22
- GeneralizedControlledAccount,
23
- Profile,
24
- } from "./account.js";
20
+ import { AccountID, GeneralizedControlledAccount, Profile } from "./account.js";
25
21
  import { Role } from "./permissions.js";
26
22
  import { base58 } from "@scure/base";
27
- import { CoList } from "./contentTypes/coList.js";
23
+ import { CoList } from "./coValues/coList.js";
24
+ import { BinaryCoStream, BinaryCoStreamMeta, CoStream } from "./coValues/coStream.js";
28
25
 
29
26
  export type GroupContent = {
30
27
  profile: CoID<Profile> | null;
@@ -38,7 +35,7 @@ export type GroupContent = {
38
35
  };
39
36
 
40
37
  export function expectGroupContent(
41
- content: ContentType
38
+ content: CoValueImpl
42
39
  ): CoMap<GroupContent, JsonObject | null> {
43
40
  if (content.type !== "comap") {
44
41
  throw new Error("Expected map");
@@ -47,43 +44,71 @@ export function expectGroupContent(
47
44
  return content as CoMap<GroupContent, JsonObject | null>;
48
45
  }
49
46
 
47
+ /** A `Group` is a scope for permissions of its members (`"reader" | "writer" | "admin"`), applying to objects owned by that group.
48
+ *
49
+ * A `Group` object exposes methods for permission management and allows you to create new CoValues owned by that group.
50
+ *
51
+ * (Internally, a `Group` is also just a `CoMap`, mapping member accounts to roles and containing some
52
+ * state management for making cryptographic keys available to current members)
53
+ *
54
+ * @example
55
+ * You typically get a group from a CoValue that you already have loaded:
56
+ *
57
+ * ```typescript
58
+ * const group = coMap.group;
59
+ * ```
60
+ *
61
+ * @example
62
+ * Or, you can create a new group with a `LocalNode`:
63
+ *
64
+ * ```typescript
65
+ * const localNode.createGroup();
66
+ * ```
67
+ * */
50
68
  export class Group {
51
- groupMap: CoMap<GroupContent, JsonObject | null>;
69
+ underlyingMap: CoMap<GroupContent, JsonObject | null>;
70
+ /** @internal */
52
71
  node: LocalNode;
53
72
 
73
+ /** @internal */
54
74
  constructor(
55
- groupMap: CoMap<GroupContent, JsonObject | null>,
75
+ underlyingMap: CoMap<GroupContent, JsonObject | null>,
56
76
  node: LocalNode
57
77
  ) {
58
- this.groupMap = groupMap;
78
+ this.underlyingMap = underlyingMap;
59
79
  this.node = node;
60
80
  }
61
81
 
82
+ /** Returns the `CoID` of the `Group`. */
62
83
  get id(): CoID<CoMap<GroupContent, JsonObject | null>> {
63
- return this.groupMap.id;
84
+ return this.underlyingMap.id;
64
85
  }
65
86
 
87
+ /** Returns the current role of a given account. */
66
88
  roleOf(accountID: AccountID): Role | undefined {
67
89
  return this.roleOfInternal(accountID);
68
90
  }
69
91
 
70
92
  /** @internal */
71
93
  roleOfInternal(accountID: AccountID | AgentID): Role | undefined {
72
- return this.groupMap.get(accountID);
94
+ return this.underlyingMap.get(accountID);
73
95
  }
74
96
 
97
+ /** Returns the role of the current account in the group. */
75
98
  myRole(): Role | undefined {
76
99
  return this.roleOfInternal(this.node.account.id);
77
100
  }
78
101
 
102
+ /** Directly grants a new member a role in the group. The current account must be an
103
+ * admin to be able to do so. Throws otherwise. */
79
104
  addMember(accountID: AccountID, role: Role) {
80
105
  this.addMemberInternal(accountID, role);
81
106
  }
82
107
 
83
108
  /** @internal */
84
109
  addMemberInternal(accountID: AccountID | AgentID, role: Role) {
85
- this.groupMap = this.groupMap.edit((map) => {
86
- const currentReadKey = this.groupMap.coValue.getCurrentReadKey();
110
+ this.underlyingMap = this.underlyingMap.edit((map) => {
111
+ const currentReadKey = this.underlyingMap.core.getCurrentReadKey();
87
112
 
88
113
  if (!currentReadKey.secret) {
89
114
  throw new Error("Can't add member without read key secret");
@@ -104,11 +129,11 @@ export class Group {
104
129
  `${currentReadKey.id}_for_${accountID}`,
105
130
  seal(
106
131
  currentReadKey.secret,
107
- this.groupMap.coValue.node.account.currentSealerSecret(),
132
+ this.underlyingMap.core.node.account.currentSealerSecret(),
108
133
  getAgentSealerID(agent),
109
134
  {
110
- in: this.groupMap.coValue.id,
111
- tx: this.groupMap.coValue.nextTransactionID(),
135
+ in: this.underlyingMap.core.id,
136
+ tx: this.underlyingMap.core.nextTransactionID(),
112
137
  }
113
138
  ),
114
139
  "trusting"
@@ -116,30 +141,24 @@ export class Group {
116
141
  });
117
142
  }
118
143
 
119
- createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
120
- const secretSeed = newRandomSecretSeed();
121
-
122
- const inviteSecret = agentSecretFromSecretSeed(secretSeed);
123
- const inviteID = getAgentID(inviteSecret);
124
-
125
- this.addMemberInternal(inviteID, `${role}Invite` as Role);
126
-
127
- return inviteSecretFromSecretSeed(secretSeed);
128
- }
129
-
144
+ /** @internal */
130
145
  rotateReadKey() {
131
- const currentlyPermittedReaders = this.groupMap.keys().filter((key) => {
132
- if (key.startsWith("co_") || isAgentID(key)) {
133
- const role = this.groupMap.get(key);
134
- return (
135
- role === "admin" || role === "writer" || role === "reader"
136
- );
137
- } else {
138
- return false;
139
- }
140
- }) as (AccountID | AgentID)[];
141
-
142
- const maybeCurrentReadKey = this.groupMap.coValue.getCurrentReadKey();
146
+ const currentlyPermittedReaders = this.underlyingMap
147
+ .keys()
148
+ .filter((key) => {
149
+ if (key.startsWith("co_") || isAgentID(key)) {
150
+ const role = this.underlyingMap.get(key);
151
+ return (
152
+ role === "admin" ||
153
+ role === "writer" ||
154
+ role === "reader"
155
+ );
156
+ } else {
157
+ return false;
158
+ }
159
+ }) as (AccountID | AgentID)[];
160
+
161
+ const maybeCurrentReadKey = this.underlyingMap.core.getCurrentReadKey();
143
162
 
144
163
  if (!maybeCurrentReadKey.secret) {
145
164
  throw new Error(
@@ -154,7 +173,7 @@ export class Group {
154
173
 
155
174
  const newReadKey = newRandomKeySecret();
156
175
 
157
- this.groupMap = this.groupMap.edit((map) => {
176
+ this.underlyingMap = this.underlyingMap.edit((map) => {
158
177
  for (const readerID of currentlyPermittedReaders) {
159
178
  const reader = this.node.resolveAccountAgent(
160
179
  readerID,
@@ -165,11 +184,11 @@ export class Group {
165
184
  `${newReadKey.id}_for_${readerID}`,
166
185
  seal(
167
186
  newReadKey.secret,
168
- this.groupMap.coValue.node.account.currentSealerSecret(),
187
+ this.underlyingMap.core.node.account.currentSealerSecret(),
169
188
  getAgentSealerID(reader),
170
189
  {
171
- in: this.groupMap.coValue.id,
172
- tx: this.groupMap.coValue.nextTransactionID(),
190
+ in: this.underlyingMap.core.id,
191
+ tx: this.underlyingMap.core.nextTransactionID(),
173
192
  }
174
193
  ),
175
194
  "trusting"
@@ -189,19 +208,36 @@ export class Group {
189
208
  });
190
209
  }
191
210
 
211
+ /** Strips the specified member of all roles (preventing future writes in
212
+ * the group and owned values) and rotates the read encryption key for that group
213
+ * (preventing reads of new content in the group and owned values) */
192
214
  removeMember(accountID: AccountID) {
193
215
  this.removeMemberInternal(accountID);
194
216
  }
195
217
 
196
218
  /** @internal */
197
219
  removeMemberInternal(accountID: AccountID | AgentID) {
198
- this.groupMap = this.groupMap.edit((map) => {
220
+ this.underlyingMap = this.underlyingMap.edit((map) => {
199
221
  map.set(accountID, "revoked", "trusting");
200
222
  });
201
223
 
202
224
  this.rotateReadKey();
203
225
  }
204
226
 
227
+ /** 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. */
228
+ createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
229
+ const secretSeed = newRandomSecretSeed();
230
+
231
+ const inviteSecret = agentSecretFromSecretSeed(secretSeed);
232
+ const inviteID = getAgentID(inviteSecret);
233
+
234
+ this.addMemberInternal(inviteID, `${role}Invite` as Role);
235
+
236
+ return inviteSecretFromSecretSeed(secretSeed);
237
+ }
238
+
239
+ /** Creates a new `CoMap` within this group, with the specified specialized
240
+ * `CoMap` type `M` and optional static metadata. */
205
241
  createMap<M extends CoMap<{ [key: string]: JsonValue }, JsonObject | null>>(
206
242
  meta?: M["meta"]
207
243
  ): M {
@@ -210,7 +246,7 @@ export class Group {
210
246
  type: "comap",
211
247
  ruleset: {
212
248
  type: "ownedByGroup",
213
- group: this.groupMap.id,
249
+ group: this.underlyingMap.id,
214
250
  },
215
251
  meta: meta || null,
216
252
  ...createdNowUnique(),
@@ -218,6 +254,8 @@ export class Group {
218
254
  .getCurrentContent() as M;
219
255
  }
220
256
 
257
+ /** Creates a new `CoList` within this group, with the specified specialized
258
+ * `CoList` type `L` and optional static metadata. */
221
259
  createList<L extends CoList<JsonValue, JsonObject | null>>(
222
260
  meta?: L["meta"]
223
261
  ): L {
@@ -226,7 +264,7 @@ export class Group {
226
264
  type: "colist",
227
265
  ruleset: {
228
266
  type: "ownedByGroup",
229
- group: this.groupMap.id,
267
+ group: this.underlyingMap.id,
230
268
  },
231
269
  meta: meta || null,
232
270
  ...createdNowUnique(),
@@ -234,6 +272,38 @@ export class Group {
234
272
  .getCurrentContent() as L;
235
273
  }
236
274
 
275
+ createStream<C extends CoStream<JsonValue, JsonObject | null>>(
276
+ meta?: C["meta"]
277
+ ): C {
278
+ return this.node
279
+ .createCoValue({
280
+ type: "costream",
281
+ ruleset: {
282
+ type: "ownedByGroup",
283
+ group: this.underlyingMap.id,
284
+ },
285
+ meta: meta || null,
286
+ ...createdNowUnique(),
287
+ })
288
+ .getCurrentContent() as C;
289
+ }
290
+
291
+ createBinaryStream<
292
+ C extends BinaryCoStream<BinaryCoStreamMeta>
293
+ >(meta: C["meta"] = { type: "binary" }): C {
294
+ return this.node
295
+ .createCoValue({
296
+ type: "costream",
297
+ ruleset: {
298
+ type: "ownedByGroup",
299
+ group: this.underlyingMap.id,
300
+ },
301
+ meta: meta,
302
+ ...createdNowUnique(),
303
+ })
304
+ .getCurrentContent() as C;
305
+ }
306
+
237
307
  /** @internal */
238
308
  testWithDifferentAccount(
239
309
  account: GeneralizedControlledAccount,
@@ -241,7 +311,7 @@ export class Group {
241
311
  ): Group {
242
312
  return new Group(
243
313
  expectGroupContent(
244
- this.groupMap.coValue
314
+ this.underlyingMap.core
245
315
  .testWithDifferentAccount(account, sessionId)
246
316
  .getCurrentContent()
247
317
  ),
package/src/index.ts CHANGED
@@ -1,7 +1,14 @@
1
- import { CoValue, newRandomSessionID } from "./coValue.js";
1
+ import { CoValueCore, newRandomSessionID } from "./coValueCore.js";
2
2
  import { LocalNode } from "./node.js";
3
- import { CoMap } from "./contentTypes/coMap.js";
4
- import { CoList } from "./contentTypes/coList.js";
3
+ import type { CoValue, ReadableCoValue } from "./coValue.js";
4
+ import { CoMap, WriteableCoMap } from "./coValues/coMap.js";
5
+ import { CoList, WriteableCoList } from "./coValues/coList.js";
6
+ import {
7
+ CoStream,
8
+ WriteableCoStream,
9
+ BinaryCoStream,
10
+ WriteableBinaryCoStream,
11
+ } from "./coValues/coStream.js";
5
12
  import {
6
13
  agentSecretFromBytes,
7
14
  agentSecretToBytes,
@@ -15,24 +22,20 @@ import {
15
22
  import { connectedPeers } from "./streamUtils.js";
16
23
  import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
17
24
  import { rawCoIDtoBytes, rawCoIDfromBytes } from "./ids.js";
18
- import { Group, expectGroupContent } from "./group.js"
25
+ import { Group, expectGroupContent } from "./group.js";
19
26
 
20
27
  import type { SessionID, AgentID } from "./ids.js";
21
- import type { CoID, ContentType } from "./contentType.js";
28
+ import type { CoID, CoValueImpl } from "./coValue.js";
29
+ import type { BinaryChunkInfo, BinaryCoStreamMeta } from "./coValues/coStream.js";
22
30
  import type { JsonValue } from "./jsonValue.js";
23
31
  import type { SyncMessage, Peer } from "./sync.js";
24
32
  import type { AgentSecret } from "./crypto.js";
25
- import type {
26
- AccountID,
27
- AccountContent,
28
- ProfileContent,
29
- ProfileMeta,
30
- Profile,
31
- } from "./account.js";
33
+ import type { AccountID, Profile } from "./account.js";
32
34
  import type { InviteSecret } from "./group.js";
33
35
 
34
- type Value = JsonValue | ContentType;
36
+ type Value = JsonValue | CoValueImpl;
35
37
 
38
+ /** @hidden */
36
39
  export const cojsonInternals = {
37
40
  agentSecretFromBytes,
38
41
  agentSecretToBytes,
@@ -46,35 +49,42 @@ export const cojsonInternals = {
46
49
  agentSecretFromSecretSeed,
47
50
  secretSeedLength,
48
51
  shortHashLength,
49
- expectGroupContent
52
+ expectGroupContent,
50
53
  };
51
54
 
52
55
  export {
53
56
  LocalNode,
54
- CoValue,
57
+ Group,
55
58
  CoMap,
59
+ WriteableCoMap,
56
60
  CoList,
61
+ WriteableCoList,
62
+ CoStream,
63
+ WriteableCoStream,
64
+ BinaryCoStream,
65
+ WriteableBinaryCoStream,
66
+ CoValueCore,
57
67
  AnonymousControlledAccount,
58
68
  ControlledAccount,
59
- Group
60
69
  };
61
70
 
62
71
  export type {
63
72
  Value,
64
73
  JsonValue,
65
- ContentType,
74
+ CoValue,
75
+ ReadableCoValue,
76
+ CoValueImpl,
66
77
  CoID,
67
- AgentSecret,
68
- SessionID,
69
- SyncMessage,
70
- AgentID,
71
78
  AccountID,
72
- Peer,
73
- AccountContent,
74
79
  Profile,
75
- ProfileContent,
76
- ProfileMeta,
77
- InviteSecret
80
+ SessionID,
81
+ Peer,
82
+ BinaryChunkInfo,
83
+ BinaryCoStreamMeta,
84
+ AgentID,
85
+ AgentSecret,
86
+ InviteSecret,
87
+ SyncMessage,
78
88
  };
79
89
 
80
90
  // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -84,8 +94,13 @@ export namespace CojsonInternalTypes {
84
94
  export type KnownStateMessage = import("./sync.js").KnownStateMessage;
85
95
  export type LoadMessage = import("./sync.js").LoadMessage;
86
96
  export type NewContentMessage = import("./sync.js").NewContentMessage;
87
- export type CoValueHeader = import("./coValue.js").CoValueHeader;
88
- export type Transaction = import("./coValue.js").Transaction;
97
+ export type CoValueHeader = import("./coValueCore.js").CoValueHeader;
98
+ export type Transaction = import("./coValueCore.js").Transaction;
89
99
  export type Signature = import("./crypto.js").Signature;
90
100
  export type RawCoID = import("./ids.js").RawCoID;
101
+ export type AccountContent = import("./account.js").AccountContent;
102
+ export type ProfileContent = import("./account.js").ProfileContent;
103
+ export type ProfileMeta = import("./account.js").ProfileMeta;
104
+ export type SealerSecret = import("./crypto.js").SealerSecret;
105
+ export type SignerSecret = import("./crypto.js").SignerSecret;
91
106
  }
package/src/node.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  newRandomKeySecret,
10
10
  seal,
11
11
  } from "./crypto.js";
12
- import { CoValue, CoValueHeader, newRandomSessionID } from "./coValue.js";
12
+ import { CoValueCore, CoValueHeader, newRandomSessionID } from "./coValueCore.js";
13
13
  import {
14
14
  InviteSecret,
15
15
  Group,
@@ -19,7 +19,7 @@ import {
19
19
  } from "./group.js";
20
20
  import { Peer, SyncManager } from "./sync.js";
21
21
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
22
- import { CoID, ContentType } from "./contentType.js";
22
+ import { CoID, CoValueImpl } from "./coValue.js";
23
23
  import {
24
24
  Account,
25
25
  AccountMeta,
@@ -32,8 +32,19 @@ import {
32
32
  AccountContent,
33
33
  AccountMap,
34
34
  } from "./account.js";
35
- import { CoMap } from "./index.js";
35
+ import { CoMap } from "./coValues/coMap.js";
36
36
 
37
+ /** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
38
+
39
+ A `LocalNode` can have peers that it syncs to, for example some form of local persistence, or a sync server, such as `sync.jazz.tools` (Jazz Global Mesh).
40
+
41
+ @example
42
+ You typically get hold of a `LocalNode` using `jazz-react`'s `useJazz()`:
43
+
44
+ ```typescript
45
+ const { localNode } = useJazz();
46
+ ```
47
+ */
37
48
  export class LocalNode {
38
49
  /** @internal */
39
50
  coValues: { [key: RawCoID]: CoValueState } = {};
@@ -111,8 +122,8 @@ export class LocalNode {
111
122
  }
112
123
 
113
124
  /** @internal */
114
- createCoValue(header: CoValueHeader): CoValue {
115
- const coValue = new CoValue(header, this);
125
+ createCoValue(header: CoValueHeader): CoValueCore {
126
+ const coValue = new CoValueCore(header, this);
116
127
  this.coValues[coValue.id] = { state: "loaded", coValue: coValue };
117
128
 
118
129
  void this.sync.syncCoValue(coValue);
@@ -121,7 +132,7 @@ export class LocalNode {
121
132
  }
122
133
 
123
134
  /** @internal */
124
- loadCoValue(id: RawCoID): Promise<CoValue> {
135
+ loadCoValue(id: RawCoID): Promise<CoValueCore> {
125
136
  let entry = this.coValues[id];
126
137
  if (!entry) {
127
138
  entry = newLoadingState();
@@ -136,10 +147,19 @@ export class LocalNode {
136
147
  return entry.done;
137
148
  }
138
149
 
139
- async load<T extends ContentType>(id: CoID<T>): Promise<T> {
150
+ /**
151
+ * Loads a CoValue's content, syncing from peers as necessary and resolving the returned
152
+ * promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
153
+ * for listening to subsequent updates to the CoValue.
154
+ */
155
+ async load<T extends CoValueImpl>(id: CoID<T>): Promise<T> {
140
156
  return (await this.loadCoValue(id)).getCurrentContent() as T;
141
157
  }
142
158
 
159
+ /**
160
+ * Loads a profile associated with an account. `Profile` is at least a `CoMap<{string: name}>`,
161
+ * but might contain other, app-specific properties.
162
+ */
143
163
  async loadProfile(id: AccountID): Promise<Profile> {
144
164
  const account = await this.load<AccountMap>(id);
145
165
  const profileID = account.get("profile");
@@ -152,20 +172,20 @@ export class LocalNode {
152
172
  ).getCurrentContent() as Profile;
153
173
  }
154
174
 
155
- async acceptInvite<T extends ContentType>(
175
+ async acceptInvite<T extends CoValueImpl>(
156
176
  groupOrOwnedValueID: CoID<T>,
157
177
  inviteSecret: InviteSecret
158
178
  ): Promise<void> {
159
179
  const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
160
180
 
161
- if (groupOrOwnedValue.coValue.header.ruleset.type === "ownedByGroup") {
181
+ if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
162
182
  return this.acceptInvite(
163
- groupOrOwnedValue.coValue.header.ruleset.group as CoID<
183
+ groupOrOwnedValue.core.header.ruleset.group as CoID<
164
184
  CoMap<GroupContent>
165
185
  >,
166
186
  inviteSecret
167
187
  );
168
- } else if (groupOrOwnedValue.coValue.header.ruleset.type !== "group") {
188
+ } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
169
189
  throw new Error("Can only accept invites to groups");
170
190
  }
171
191
 
@@ -177,7 +197,7 @@ export class LocalNode {
177
197
  const inviteAgentID = getAgentID(inviteAgentSecret);
178
198
 
179
199
  const inviteRole = await new Promise((resolve, reject) => {
180
- group.groupMap.subscribe((groupMap) => {
200
+ group.underlyingMap.subscribe((groupMap) => {
181
201
  const role = groupMap.get(inviteAgentID);
182
202
  if (role) {
183
203
  resolve(role);
@@ -196,7 +216,7 @@ export class LocalNode {
196
216
  throw new Error("No invite found");
197
217
  }
198
218
 
199
- const existingRole = group.groupMap.get(this.account.id);
219
+ const existingRole = group.underlyingMap.get(this.account.id);
200
220
 
201
221
  if (
202
222
  existingRole === "admin" ||
@@ -222,16 +242,16 @@ export class LocalNode {
222
242
  : "reader"
223
243
  );
224
244
 
225
- group.groupMap.coValue._sessions = groupAsInvite.groupMap.coValue.sessions;
226
- group.groupMap.coValue._cachedContent = undefined;
245
+ group.underlyingMap.core._sessions = groupAsInvite.underlyingMap.core.sessions;
246
+ group.underlyingMap.core._cachedContent = undefined;
227
247
 
228
- for (const groupListener of group.groupMap.coValue.listeners) {
229
- groupListener(group.groupMap.coValue.getCurrentContent());
248
+ for (const groupListener of group.underlyingMap.core.listeners) {
249
+ groupListener(group.underlyingMap.core.getCurrentContent());
230
250
  }
231
251
  }
232
252
 
233
253
  /** @internal */
234
- expectCoValueLoaded(id: RawCoID, expectation?: string): CoValue {
254
+ expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
235
255
  const entry = this.coValues[id];
236
256
  if (!entry) {
237
257
  throw new Error(
@@ -284,7 +304,7 @@ export class LocalNode {
284
304
  account.node
285
305
  );
286
306
 
287
- accountAsGroup.groupMap.edit((editable) => {
307
+ accountAsGroup.underlyingMap.edit((editable) => {
288
308
  editable.set(getAgentID(agentSecret), "admin", "trusting");
289
309
 
290
310
  const readKey = newRandomKeySecret();
@@ -320,13 +340,13 @@ export class LocalNode {
320
340
  editable.set("name", name, "trusting");
321
341
  });
322
342
 
323
- accountAsGroup.groupMap.edit((editable) => {
343
+ accountAsGroup.underlyingMap.edit((editable) => {
324
344
  editable.set("profile", profile.id, "trusting");
325
345
  });
326
346
 
327
347
  const accountOnThisNode = this.expectCoValueLoaded(account.id);
328
348
 
329
- accountOnThisNode._sessions = {...accountAsGroup.groupMap.coValue.sessions};
349
+ accountOnThisNode._sessions = {...accountAsGroup.underlyingMap.core.sessions};
330
350
  accountOnThisNode._cachedContent = undefined;
331
351
 
332
352
  return controlledAccount;
@@ -360,6 +380,7 @@ export class LocalNode {
360
380
  ).getCurrentAgentID();
361
381
  }
362
382
 
383
+ /** Creates a new group (with the current account as the group's first admin). */
363
384
  createGroup(): Group {
364
385
  const groupCoValue = this.createCoValue({
365
386
  type: "comap",
@@ -422,7 +443,7 @@ export class LocalNode {
422
443
  continue;
423
444
  }
424
445
 
425
- const newCoValue = new CoValue(entry.coValue.header, newNode, {...entry.coValue.sessions});
446
+ const newCoValue = new CoValueCore(entry.coValue.header, newNode, {...entry.coValue.sessions});
426
447
 
427
448
  newNode.coValues[coValueID as RawCoID] = {
428
449
  state: "loaded",
@@ -441,16 +462,16 @@ export class LocalNode {
441
462
  type CoValueState =
442
463
  | {
443
464
  state: "loading";
444
- done: Promise<CoValue>;
445
- resolve: (coValue: CoValue) => void;
465
+ done: Promise<CoValueCore>;
466
+ resolve: (coValue: CoValueCore) => void;
446
467
  }
447
- | { state: "loaded"; coValue: CoValue };
468
+ | { state: "loaded"; coValue: CoValueCore };
448
469
 
449
470
  /** @internal */
450
471
  export function newLoadingState(): CoValueState {
451
- let resolve: (coValue: CoValue) => void;
472
+ let resolve: (coValue: CoValueCore) => void;
452
473
 
453
- const promise = new Promise<CoValue>((r) => {
474
+ const promise = new Promise<CoValueCore>((r) => {
454
475
  resolve = r;
455
476
  });
456
477