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.
- package/dist/account.d.ts +6 -3
- package/dist/account.js +4 -2
- package/dist/account.js.map +1 -1
- package/dist/coValue.d.ts +44 -80
- package/dist/coValue.js +4 -348
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore.d.ts +84 -0
- package/dist/coValueCore.js +356 -0
- package/dist/coValueCore.js.map +1 -0
- package/dist/coValues/coList.d.ts +114 -0
- package/dist/{contentTypes → coValues}/coList.js +59 -19
- package/dist/coValues/coList.js.map +1 -0
- package/dist/{contentTypes → coValues}/coMap.d.ts +25 -7
- package/dist/{contentTypes → coValues}/coMap.js +34 -15
- package/dist/coValues/coMap.js.map +1 -0
- package/dist/coValues/coStream.d.ts +69 -0
- package/dist/coValues/coStream.js +131 -0
- package/dist/coValues/coStream.js.map +1 -0
- package/dist/coValues/static.d.ts +14 -0
- package/dist/coValues/static.js +20 -0
- package/dist/coValues/static.js.map +1 -0
- package/dist/group.d.ts +57 -9
- package/dist/group.js +94 -28
- package/dist/group.js.map +1 -1
- package/dist/index.d.ts +19 -10
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +59 -5
- package/dist/node.js +36 -15
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts +3 -3
- package/dist/sync.js +2 -2
- package/dist/sync.js.map +1 -1
- package/dist/testUtils.d.ts +2 -2
- package/dist/testUtils.js +1 -1
- package/dist/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/account.test.ts +1 -1
- package/src/account.ts +8 -5
- package/src/coValue.test.ts +335 -129
- package/src/coValue.ts +52 -576
- package/src/coValueCore.test.ts +180 -0
- package/src/coValueCore.ts +592 -0
- package/src/{contentTypes → coValues}/coList.ts +91 -42
- package/src/{contentTypes → coValues}/coMap.ts +40 -20
- package/src/coValues/coStream.ts +249 -0
- package/src/coValues/static.ts +31 -0
- package/src/group.test.ts +47 -0
- package/src/group.ts +120 -50
- package/src/index.ts +43 -28
- package/src/node.ts +48 -27
- package/src/permissions.test.ts +32 -32
- package/src/permissions.ts +5 -5
- package/src/sync.test.ts +77 -77
- package/src/sync.ts +5 -5
- package/src/testUtils.ts +1 -1
- package/tsconfig.json +1 -2
- package/dist/contentType.d.ts +0 -15
- package/dist/contentType.js +0 -7
- package/dist/contentType.js.map +0 -1
- package/dist/contentTypes/coList.d.ts +0 -77
- package/dist/contentTypes/coList.js.map +0 -1
- package/dist/contentTypes/coMap.js.map +0 -1
- package/dist/contentTypes/coStream.d.ts +0 -11
- package/dist/contentTypes/coStream.js +0 -16
- package/dist/contentTypes/coStream.js.map +0 -1
- package/dist/contentTypes/static.d.ts +0 -11
- package/dist/contentTypes/static.js +0 -14
- package/dist/contentTypes/static.js.map +0 -1
- package/src/contentType.test.ts +0 -284
- package/src/contentType.ts +0 -26
- package/src/contentTypes/coStream.ts +0 -24
- 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,
|
|
2
|
-
import { CoMap } from "./
|
|
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 "./
|
|
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:
|
|
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
|
-
|
|
69
|
+
underlyingMap: CoMap<GroupContent, JsonObject | null>;
|
|
70
|
+
/** @internal */
|
|
52
71
|
node: LocalNode;
|
|
53
72
|
|
|
73
|
+
/** @internal */
|
|
54
74
|
constructor(
|
|
55
|
-
|
|
75
|
+
underlyingMap: CoMap<GroupContent, JsonObject | null>,
|
|
56
76
|
node: LocalNode
|
|
57
77
|
) {
|
|
58
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
86
|
-
const currentReadKey = this.
|
|
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.
|
|
132
|
+
this.underlyingMap.core.node.account.currentSealerSecret(),
|
|
108
133
|
getAgentSealerID(agent),
|
|
109
134
|
{
|
|
110
|
-
in: this.
|
|
111
|
-
tx: this.
|
|
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
|
-
|
|
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.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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.
|
|
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.
|
|
187
|
+
this.underlyingMap.core.node.account.currentSealerSecret(),
|
|
169
188
|
getAgentSealerID(reader),
|
|
170
189
|
{
|
|
171
|
-
in: this.
|
|
172
|
-
tx: this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
1
|
+
import { CoValueCore, newRandomSessionID } from "./coValueCore.js";
|
|
2
2
|
import { LocalNode } from "./node.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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,
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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("./
|
|
88
|
-
export type Transaction = import("./
|
|
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 {
|
|
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,
|
|
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 "./
|
|
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):
|
|
115
|
-
const coValue = new
|
|
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<
|
|
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
|
-
|
|
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
|
|
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.
|
|
181
|
+
if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
|
|
162
182
|
return this.acceptInvite(
|
|
163
|
-
groupOrOwnedValue.
|
|
183
|
+
groupOrOwnedValue.core.header.ruleset.group as CoID<
|
|
164
184
|
CoMap<GroupContent>
|
|
165
185
|
>,
|
|
166
186
|
inviteSecret
|
|
167
187
|
);
|
|
168
|
-
} else if (groupOrOwnedValue.
|
|
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.
|
|
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.
|
|
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.
|
|
226
|
-
group.
|
|
245
|
+
group.underlyingMap.core._sessions = groupAsInvite.underlyingMap.core.sessions;
|
|
246
|
+
group.underlyingMap.core._cachedContent = undefined;
|
|
227
247
|
|
|
228
|
-
for (const groupListener of group.
|
|
229
|
-
groupListener(group.
|
|
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):
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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<
|
|
445
|
-
resolve: (coValue:
|
|
465
|
+
done: Promise<CoValueCore>;
|
|
466
|
+
resolve: (coValue: CoValueCore) => void;
|
|
446
467
|
}
|
|
447
|
-
| { state: "loaded"; coValue:
|
|
468
|
+
| { state: "loaded"; coValue: CoValueCore };
|
|
448
469
|
|
|
449
470
|
/** @internal */
|
|
450
471
|
export function newLoadingState(): CoValueState {
|
|
451
|
-
let resolve: (coValue:
|
|
472
|
+
let resolve: (coValue: CoValueCore) => void;
|
|
452
473
|
|
|
453
|
-
const promise = new Promise<
|
|
474
|
+
const promise = new Promise<CoValueCore>((r) => {
|
|
454
475
|
resolve = r;
|
|
455
476
|
});
|
|
456
477
|
|