jazz-tools 0.9.1 → 0.9.9
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/{chunk-7LENDMTN.js → chunk-VQZOWIPU.js} +915 -376
- package/dist/chunk-VQZOWIPU.js.map +1 -0
- package/dist/index.native.js +7 -1
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.js +7 -1
- package/dist/index.web.js.map +1 -1
- package/dist/testing.js +10 -2
- package/dist/testing.js.map +1 -1
- package/package.json +2 -2
- package/src/coValues/account.ts +41 -3
- package/src/coValues/coFeed.ts +112 -20
- package/src/coValues/coList.ts +47 -15
- package/src/coValues/coMap.ts +51 -16
- package/src/coValues/coPlainText.ts +247 -0
- package/src/coValues/coRichText.ts +635 -0
- package/src/coValues/deepLoading.ts +11 -2
- package/src/coValues/group.ts +49 -17
- package/src/coValues/inbox.ts +7 -2
- package/src/coValues/interfaces.ts +112 -9
- package/src/exports.ts +13 -8
- package/src/implementation/activeAccountContext.ts +33 -0
- package/src/implementation/createContext.ts +8 -0
- package/src/internal.ts +4 -4
- package/src/testing.ts +9 -0
- package/src/tests/account.test.ts +1 -0
- package/src/tests/coFeed.test.ts +13 -0
- package/src/tests/coPlainText.test.ts +161 -0
- package/src/tests/coRichText.test.ts +934 -0
- package/src/tests/interfaces.test.ts +90 -0
- package/dist/chunk-7LENDMTN.js.map +0 -1
package/src/coValues/group.ts
CHANGED
@@ -13,9 +13,9 @@ import {
|
|
13
13
|
MembersSym,
|
14
14
|
Ref,
|
15
15
|
ensureCoValueLoaded,
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
loadCoValueWithoutMe,
|
17
|
+
parseGroupCreateOptions,
|
18
|
+
subscribeToCoValueWithoutMe,
|
19
19
|
subscribeToExistingCoValue,
|
20
20
|
} from "../internal.js";
|
21
21
|
import { AccountAndGroupProxyHandler, isControlledAccount } from "./account.js";
|
@@ -124,9 +124,9 @@ export class Group extends CoValueBase implements CoValue {
|
|
124
124
|
|
125
125
|
static create<G extends Group>(
|
126
126
|
this: CoValueClass<G>,
|
127
|
-
options
|
127
|
+
options?: { owner: Account } | Account,
|
128
128
|
) {
|
129
|
-
return new this(
|
129
|
+
return new this(parseGroupCreateOptions(options));
|
130
130
|
}
|
131
131
|
|
132
132
|
myRole(): Role | undefined {
|
@@ -179,24 +179,56 @@ export class Group extends CoValueBase implements CoValue {
|
|
179
179
|
}
|
180
180
|
|
181
181
|
/** @category Subscription & Loading */
|
182
|
-
static load<
|
183
|
-
this: CoValueClass<
|
184
|
-
id: ID<
|
182
|
+
static load<C extends Group, Depth>(
|
183
|
+
this: CoValueClass<C>,
|
184
|
+
id: ID<C>,
|
185
|
+
depth: Depth & DepthsIn<C>,
|
186
|
+
): Promise<DeeplyLoaded<C, Depth> | undefined>;
|
187
|
+
static load<C extends Group, Depth>(
|
188
|
+
this: CoValueClass<C>,
|
189
|
+
id: ID<C>,
|
185
190
|
as: Account,
|
186
|
-
depth: Depth & DepthsIn<
|
187
|
-
): Promise<DeeplyLoaded<
|
188
|
-
|
191
|
+
depth: Depth & DepthsIn<C>,
|
192
|
+
): Promise<DeeplyLoaded<C, Depth> | undefined>;
|
193
|
+
static load<C extends Group, Depth>(
|
194
|
+
this: CoValueClass<C>,
|
195
|
+
id: ID<C>,
|
196
|
+
asOrDepth: Account | (Depth & DepthsIn<C>),
|
197
|
+
depth?: Depth & DepthsIn<C>,
|
198
|
+
): Promise<DeeplyLoaded<C, Depth> | undefined> {
|
199
|
+
return loadCoValueWithoutMe(this, id, asOrDepth, depth);
|
189
200
|
}
|
190
201
|
|
191
202
|
/** @category Subscription & Loading */
|
192
|
-
static subscribe<
|
193
|
-
this: CoValueClass<
|
194
|
-
id: ID<
|
203
|
+
static subscribe<C extends Group, Depth>(
|
204
|
+
this: CoValueClass<C>,
|
205
|
+
id: ID<C>,
|
206
|
+
depth: Depth & DepthsIn<C>,
|
207
|
+
listener: (value: DeeplyLoaded<C, Depth>) => void,
|
208
|
+
): () => void;
|
209
|
+
static subscribe<C extends Group, Depth>(
|
210
|
+
this: CoValueClass<C>,
|
211
|
+
id: ID<C>,
|
195
212
|
as: Account,
|
196
|
-
depth: Depth & DepthsIn<
|
197
|
-
listener: (value: DeeplyLoaded<
|
213
|
+
depth: Depth & DepthsIn<C>,
|
214
|
+
listener: (value: DeeplyLoaded<C, Depth>) => void,
|
215
|
+
): () => void;
|
216
|
+
static subscribe<C extends Group, Depth>(
|
217
|
+
this: CoValueClass<C>,
|
218
|
+
id: ID<C>,
|
219
|
+
asOrDepth: Account | (Depth & DepthsIn<C>),
|
220
|
+
depthOrListener:
|
221
|
+
| (Depth & DepthsIn<C>)
|
222
|
+
| ((value: DeeplyLoaded<C, Depth>) => void),
|
223
|
+
listener?: (value: DeeplyLoaded<C, Depth>) => void,
|
198
224
|
): () => void {
|
199
|
-
return
|
225
|
+
return subscribeToCoValueWithoutMe<C, Depth>(
|
226
|
+
this,
|
227
|
+
id,
|
228
|
+
asOrDepth,
|
229
|
+
depthOrListener,
|
230
|
+
listener,
|
231
|
+
);
|
200
232
|
}
|
201
233
|
|
202
234
|
/** @category Subscription & Loading */
|
package/src/coValues/inbox.ts
CHANGED
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
SessionID,
|
8
8
|
} from "cojson";
|
9
9
|
import { CoStreamItem, RawCoStream } from "cojson";
|
10
|
+
import { activeAccountContext } from "../implementation/activeAccountContext.js";
|
10
11
|
import { type Account } from "./account.js";
|
11
12
|
import { CoValue, CoValueClass, ID, loadCoValue } from "./interfaces.js";
|
12
13
|
|
@@ -312,7 +313,9 @@ export class InboxSender<I extends CoValue, O extends CoValue | undefined> {
|
|
312
313
|
static async load<
|
313
314
|
I extends CoValue,
|
314
315
|
O extends CoValue | undefined = undefined,
|
315
|
-
>(inboxOwnerID: ID<Account>, currentAccount
|
316
|
+
>(inboxOwnerID: ID<Account>, currentAccount?: Account) {
|
317
|
+
currentAccount ||= activeAccountContext.get();
|
318
|
+
|
316
319
|
const node = currentAccount._raw.core.node;
|
317
320
|
|
318
321
|
const inboxOwnerRaw = await node.load(
|
@@ -347,7 +350,9 @@ export class InboxSender<I extends CoValue, O extends CoValue | undefined> {
|
|
347
350
|
}
|
348
351
|
}
|
349
352
|
|
350
|
-
async function acceptInvite(invite: string, account
|
353
|
+
async function acceptInvite(invite: string, account?: Account) {
|
354
|
+
account ||= activeAccountContext.get();
|
355
|
+
|
351
356
|
const id = invite.slice(0, invite.indexOf("/")) as CoID<MessagesStream>;
|
352
357
|
|
353
358
|
const inviteSecret = invite.slice(invite.indexOf("/") + 1) as InviteSecret;
|
@@ -4,6 +4,7 @@ import type {
|
|
4
4
|
RawCoValue,
|
5
5
|
} from "cojson";
|
6
6
|
import { RawAccount } from "cojson";
|
7
|
+
import { activeAccountContext } from "../implementation/activeAccountContext.js";
|
7
8
|
import { AnonymousJazzAgent } from "../implementation/anonymousJazzAgent.js";
|
8
9
|
import type { DeeplyLoaded, DepthsIn } from "../internal.js";
|
9
10
|
import {
|
@@ -154,6 +155,24 @@ export class CoValueBase implements CoValue {
|
|
154
155
|
}
|
155
156
|
}
|
156
157
|
|
158
|
+
export function loadCoValueWithoutMe<V extends CoValue, Depth>(
|
159
|
+
cls: CoValueClass<V>,
|
160
|
+
id: ID<V>,
|
161
|
+
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
162
|
+
depth?: Depth & DepthsIn<V>,
|
163
|
+
) {
|
164
|
+
if (isAccountInstance(asOrDepth) || isAnonymousAgentInstance(asOrDepth)) {
|
165
|
+
if (!depth) {
|
166
|
+
throw new Error(
|
167
|
+
"Depth is required when loading a CoValue as an Account or AnonymousJazzAgent",
|
168
|
+
);
|
169
|
+
}
|
170
|
+
return loadCoValue(cls, id, asOrDepth, depth);
|
171
|
+
}
|
172
|
+
|
173
|
+
return loadCoValue(cls, id, activeAccountContext.get(), asOrDepth);
|
174
|
+
}
|
175
|
+
|
157
176
|
export function loadCoValue<V extends CoValue, Depth>(
|
158
177
|
cls: CoValueClass<V>,
|
159
178
|
id: ID<V>,
|
@@ -189,6 +208,41 @@ export function ensureCoValueLoaded<V extends CoValue, Depth>(
|
|
189
208
|
);
|
190
209
|
}
|
191
210
|
|
211
|
+
export function subscribeToCoValueWithoutMe<V extends CoValue, Depth>(
|
212
|
+
cls: CoValueClass<V>,
|
213
|
+
id: ID<V>,
|
214
|
+
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
215
|
+
depthOrListener:
|
216
|
+
| (Depth & DepthsIn<V>)
|
217
|
+
| ((value: DeeplyLoaded<V, Depth>) => void),
|
218
|
+
listener?: (value: DeeplyLoaded<V, Depth>) => void,
|
219
|
+
) {
|
220
|
+
if (isAccountInstance(asOrDepth) || isAnonymousAgentInstance(asOrDepth)) {
|
221
|
+
if (typeof depthOrListener !== "function") {
|
222
|
+
return subscribeToCoValue<V, Depth>(
|
223
|
+
cls,
|
224
|
+
id,
|
225
|
+
asOrDepth,
|
226
|
+
depthOrListener,
|
227
|
+
listener!,
|
228
|
+
);
|
229
|
+
}
|
230
|
+
throw new Error("Invalid arguments");
|
231
|
+
}
|
232
|
+
|
233
|
+
if (typeof depthOrListener !== "function") {
|
234
|
+
throw new Error("Invalid arguments");
|
235
|
+
}
|
236
|
+
|
237
|
+
return subscribeToCoValue<V, Depth>(
|
238
|
+
cls,
|
239
|
+
id,
|
240
|
+
activeAccountContext.get(),
|
241
|
+
asOrDepth,
|
242
|
+
depthOrListener,
|
243
|
+
);
|
244
|
+
}
|
245
|
+
|
192
246
|
export function subscribeToCoValue<V extends CoValue, Depth>(
|
193
247
|
cls: CoValueClass<V>,
|
194
248
|
id: ID<V>,
|
@@ -304,20 +358,69 @@ export function subscribeToExistingCoValue<V extends CoValue, Depth>(
|
|
304
358
|
);
|
305
359
|
}
|
306
360
|
|
361
|
+
export function isAccountInstance(instance: unknown): instance is Account {
|
362
|
+
if (typeof instance !== "object" || instance === null) {
|
363
|
+
return false;
|
364
|
+
}
|
365
|
+
|
366
|
+
return "_type" in instance && instance._type === "Account";
|
367
|
+
}
|
368
|
+
|
369
|
+
export function isAnonymousAgentInstance(
|
370
|
+
instance: unknown,
|
371
|
+
): instance is AnonymousJazzAgent {
|
372
|
+
if (typeof instance !== "object" || instance === null) {
|
373
|
+
return false;
|
374
|
+
}
|
375
|
+
|
376
|
+
return "_type" in instance && instance._type === "Anonymous";
|
377
|
+
}
|
378
|
+
|
307
379
|
export function parseCoValueCreateOptions(
|
308
380
|
options:
|
309
381
|
| {
|
310
|
-
owner
|
382
|
+
owner?: Account | Group;
|
311
383
|
unique?: CoValueUniqueness["uniqueness"];
|
312
384
|
}
|
313
385
|
| Account
|
314
|
-
| Group
|
386
|
+
| Group
|
387
|
+
| undefined,
|
315
388
|
) {
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
:
|
320
|
-
|
321
|
-
|
322
|
-
|
389
|
+
const Group = RegisteredSchemas["Group"];
|
390
|
+
|
391
|
+
if (!options) {
|
392
|
+
return { owner: Group.create(), uniqueness: undefined };
|
393
|
+
}
|
394
|
+
|
395
|
+
if ("_type" in options) {
|
396
|
+
if (options._type === "Account" || options._type === "Group") {
|
397
|
+
return { owner: options, uniqueness: undefined };
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
const uniqueness = options.unique
|
402
|
+
? { uniqueness: options.unique }
|
403
|
+
: undefined;
|
404
|
+
|
405
|
+
return {
|
406
|
+
owner: options.owner ?? Group.create(),
|
407
|
+
uniqueness,
|
408
|
+
};
|
409
|
+
}
|
410
|
+
|
411
|
+
export function parseGroupCreateOptions(
|
412
|
+
options:
|
413
|
+
| {
|
414
|
+
owner?: Account;
|
415
|
+
}
|
416
|
+
| Account
|
417
|
+
| undefined,
|
418
|
+
) {
|
419
|
+
if (!options) {
|
420
|
+
return { owner: activeAccountContext.get() };
|
421
|
+
}
|
422
|
+
|
423
|
+
return "_type" in options && isAccountInstance(options)
|
424
|
+
? { owner: options }
|
425
|
+
: { owner: options.owner ?? activeAccountContext.get() };
|
323
426
|
}
|
package/src/exports.ts
CHANGED
@@ -12,27 +12,32 @@ export type { CoValue, ID } from "./internal.js";
|
|
12
12
|
|
13
13
|
export { Encoders, co } from "./internal.js";
|
14
14
|
|
15
|
-
export {
|
16
|
-
Inbox,
|
17
|
-
InboxSender,
|
18
|
-
} from "./coValues/inbox.js";
|
15
|
+
export { Inbox, InboxSender } from "./coValues/inbox.js";
|
19
16
|
|
20
17
|
export {
|
21
18
|
Account,
|
22
19
|
isControlledAccount,
|
23
20
|
type AccountClass,
|
24
21
|
} from "./coValues/account.js";
|
25
|
-
export { Group } from "./coValues/group.js";
|
26
22
|
export {
|
27
|
-
|
23
|
+
BinaryCoStream,
|
28
24
|
CoFeed,
|
25
|
+
CoStream,
|
29
26
|
FileStream,
|
30
|
-
BinaryCoStream,
|
31
27
|
} from "./coValues/coFeed.js";
|
32
28
|
export { CoList } from "./coValues/coList.js";
|
33
29
|
export { CoMap, type CoMapInit } from "./coValues/coMap.js";
|
34
|
-
export {
|
30
|
+
export { CoPlainText, type TextPos } from "./coValues/coPlainText.js";
|
31
|
+
export {
|
32
|
+
CoRichText,
|
33
|
+
Marks,
|
34
|
+
type TreeLeaf,
|
35
|
+
type TreeNode,
|
36
|
+
type ResolvedMark,
|
37
|
+
} from "./coValues/coRichText.js";
|
35
38
|
export { ImageDefinition } from "./coValues/extensions/imageDef.js";
|
39
|
+
export { Group } from "./coValues/group.js";
|
40
|
+
export { CoValueBase } from "./coValues/interfaces.js";
|
36
41
|
export { Profile } from "./coValues/profile.js";
|
37
42
|
export { SchemaUnion } from "./coValues/schemaUnion.js";
|
38
43
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import type { Account } from "../coValues/account.js";
|
2
|
+
|
3
|
+
class ActiveAccountContext {
|
4
|
+
private activeAccount: Account | null = null;
|
5
|
+
private guestMode: boolean = false;
|
6
|
+
|
7
|
+
set(account: Account) {
|
8
|
+
this.activeAccount = account;
|
9
|
+
this.guestMode = false;
|
10
|
+
}
|
11
|
+
|
12
|
+
setGuestMode() {
|
13
|
+
this.guestMode = true;
|
14
|
+
}
|
15
|
+
|
16
|
+
get() {
|
17
|
+
if (!this.activeAccount) {
|
18
|
+
if (this.guestMode) {
|
19
|
+
throw new Error(
|
20
|
+
"Something that expects a full active account was called in guest mode.",
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
throw new Error("No active account");
|
25
|
+
}
|
26
|
+
|
27
|
+
return this.activeAccount;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
export type { ActiveAccountContext };
|
32
|
+
|
33
|
+
export const activeAccountContext = new ActiveAccountContext();
|
@@ -12,6 +12,7 @@ import {
|
|
12
12
|
import { type Account, type AccountClass } from "../coValues/account.js";
|
13
13
|
import { RegisteredSchemas } from "../coValues/registeredSchemas.js";
|
14
14
|
import type { ID } from "../internal.js";
|
15
|
+
import { activeAccountContext } from "./activeAccountContext.js";
|
15
16
|
import { AnonymousJazzAgent } from "./anonymousJazzAgent.js";
|
16
17
|
|
17
18
|
export type Credentials = {
|
@@ -169,11 +170,14 @@ export async function createJazzContext<Acc extends Account>(
|
|
169
170
|
fromRaw: rawAccount,
|
170
171
|
}) as Acc;
|
171
172
|
|
173
|
+
activeAccountContext.set(account);
|
174
|
+
|
172
175
|
await account.applyMigration(creationProps);
|
173
176
|
},
|
174
177
|
});
|
175
178
|
|
176
179
|
const account = AccountSchema.fromNode(node);
|
180
|
+
activeAccountContext.set(account);
|
177
181
|
|
178
182
|
if (authResult.saveCredentials) {
|
179
183
|
await authResult.saveCredentials({
|
@@ -217,12 +221,14 @@ export async function createJazzContext<Acc extends Account>(
|
|
217
221
|
const account = new AccountSchema({
|
218
222
|
fromRaw: rawAccount,
|
219
223
|
}) as Acc;
|
224
|
+
activeAccountContext.set(account);
|
220
225
|
|
221
226
|
await account.applyMigration(creationProps);
|
222
227
|
},
|
223
228
|
});
|
224
229
|
|
225
230
|
const account = AccountSchema.fromNode(node);
|
231
|
+
activeAccountContext.set(account);
|
226
232
|
|
227
233
|
await authResult.saveCredentials({
|
228
234
|
accountID: node.account.id as unknown as ID<Account>,
|
@@ -267,6 +273,8 @@ export async function createAnonymousJazzContext({
|
|
267
273
|
node.syncManager.addPeer(peer);
|
268
274
|
}
|
269
275
|
|
276
|
+
activeAccountContext.setGuestMode();
|
277
|
+
|
270
278
|
return {
|
271
279
|
agent: new AnonymousJazzAgent(node),
|
272
280
|
done: () => {},
|
package/src/internal.ts
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
export * from "./implementation/symbols.js";
|
2
|
-
export * from "./implementation/inspect.js";
|
3
1
|
export * from "./coValues/interfaces.js";
|
2
|
+
export * from "./implementation/inspect.js";
|
3
|
+
export * from "./implementation/symbols.js";
|
4
4
|
|
5
|
-
export * from "./
|
5
|
+
export * from "./coValues/deepLoading.js";
|
6
6
|
export * from "./implementation/anonymousJazzAgent.js";
|
7
|
+
export * from "./implementation/errors.js";
|
7
8
|
export * from "./implementation/refs.js";
|
8
9
|
export * from "./implementation/schema.js";
|
9
10
|
export * from "./implementation/subscriptionScope.js";
|
10
|
-
export * from "./coValues/deepLoading.js";
|
11
11
|
|
12
12
|
export * from "./implementation/createContext.js";
|
13
13
|
|
package/src/testing.ts
CHANGED
@@ -2,6 +2,7 @@ import { AgentSecret, CryptoProvider, Peer } from "cojson";
|
|
2
2
|
import { cojsonInternals } from "cojson";
|
3
3
|
import { PureJSCrypto } from "cojson/crypto";
|
4
4
|
import { Account, type AccountClass } from "./exports.js";
|
5
|
+
import { activeAccountContext } from "./implementation/activeAccountContext.js";
|
5
6
|
import {
|
6
7
|
type AnonymousJazzAgent,
|
7
8
|
type CoValueClass,
|
@@ -42,6 +43,7 @@ class TestJSCrypto extends PureJSCrypto {
|
|
42
43
|
}
|
43
44
|
|
44
45
|
export async function createJazzTestAccount<Acc extends Account>(options?: {
|
46
|
+
isCurrentActiveAccount?: boolean;
|
45
47
|
AccountSchema?: CoValueClass<Acc>;
|
46
48
|
}): Promise<Acc> {
|
47
49
|
const AccountSchema = (options?.AccountSchema ??
|
@@ -52,10 +54,17 @@ export async function createJazzTestAccount<Acc extends Account>(options?: {
|
|
52
54
|
},
|
53
55
|
crypto: await TestJSCrypto.create(),
|
54
56
|
});
|
57
|
+
if (options?.isCurrentActiveAccount) {
|
58
|
+
activeAccountContext.set(account);
|
59
|
+
}
|
55
60
|
|
56
61
|
return account;
|
57
62
|
}
|
58
63
|
|
64
|
+
export function setActiveAccount(account: Account) {
|
65
|
+
activeAccountContext.set(account);
|
66
|
+
}
|
67
|
+
|
59
68
|
export async function createJazzTestGuest() {
|
60
69
|
const ctx = await createAnonymousJazzContext({
|
61
70
|
crypto: await PureJSCrypto.create(),
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { expect, test } from "vitest";
|
2
2
|
import { CoMap, co } from "../exports.js";
|
3
|
+
import { createJazzTestAccount } from "../testing.js";
|
3
4
|
import { setupTwoNodes } from "./utils.js";
|
4
5
|
|
5
6
|
test("waitForAllCoValuesSync should resolve when all the values are synced", async () => {
|
package/src/tests/coFeed.test.ts
CHANGED
@@ -14,6 +14,7 @@ import {
|
|
14
14
|
isControlledAccount,
|
15
15
|
} from "../index.web.js";
|
16
16
|
import { randomSessionProvider } from "../internal.js";
|
17
|
+
import { createJazzTestAccount } from "../testing.js";
|
17
18
|
import { setupTwoNodes } from "./utils.js";
|
18
19
|
|
19
20
|
const Crypto = await WasmCrypto.create();
|
@@ -499,4 +500,16 @@ describe("waitForSync", async () => {
|
|
499
500
|
|
500
501
|
expect(loadedStream).not.toBe("unavailable");
|
501
502
|
});
|
503
|
+
|
504
|
+
test("should rely on the current active account if no account is provided", async () => {
|
505
|
+
const account = await createJazzTestAccount({
|
506
|
+
isCurrentActiveAccount: true,
|
507
|
+
});
|
508
|
+
|
509
|
+
const stream = FileStream.create();
|
510
|
+
expect(stream._owner._type).toEqual("Group");
|
511
|
+
expect(stream._owner.castAs(Group)._raw.roleOf(account._raw.id)).toEqual(
|
512
|
+
"admin",
|
513
|
+
);
|
514
|
+
});
|
502
515
|
});
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { connectedPeers } from "cojson/src/streamUtils.js";
|
2
|
+
import { describe, expect, test } from "vitest";
|
3
|
+
import {
|
4
|
+
Account,
|
5
|
+
CoPlainText,
|
6
|
+
WasmCrypto,
|
7
|
+
cojsonInternals,
|
8
|
+
createJazzContext,
|
9
|
+
fixedCredentialsAuth,
|
10
|
+
isControlledAccount,
|
11
|
+
} from "../index.web.js";
|
12
|
+
import { randomSessionProvider } from "../internal.js";
|
13
|
+
|
14
|
+
const Crypto = await WasmCrypto.create();
|
15
|
+
|
16
|
+
describe("CoPlainText", () => {
|
17
|
+
const initNodeAndText = async () => {
|
18
|
+
const me = await Account.create({
|
19
|
+
creationProps: { name: "Hermes Puggington" },
|
20
|
+
crypto: Crypto,
|
21
|
+
});
|
22
|
+
|
23
|
+
const text = CoPlainText.create("hello world", { owner: me });
|
24
|
+
|
25
|
+
return { me, text };
|
26
|
+
};
|
27
|
+
|
28
|
+
describe("Simple CoPlainText operations", async () => {
|
29
|
+
const { me, text } = await initNodeAndText();
|
30
|
+
|
31
|
+
test("Construction", () => {
|
32
|
+
expect(text + "").toEqual("hello world");
|
33
|
+
});
|
34
|
+
|
35
|
+
describe("Mutation", () => {
|
36
|
+
test("insertion", () => {
|
37
|
+
const text = CoPlainText.create("hello world", { owner: me });
|
38
|
+
|
39
|
+
text.insertAfter(5, " cruel");
|
40
|
+
expect(text + "").toEqual("hello cruel world");
|
41
|
+
|
42
|
+
text.insertAfter(0, "Hello, ");
|
43
|
+
expect(text + "").toEqual("Hello, hello cruel world");
|
44
|
+
});
|
45
|
+
|
46
|
+
test("deletion", () => {
|
47
|
+
const text = CoPlainText.create("hello world", { owner: me });
|
48
|
+
|
49
|
+
text.deleteRange({ from: 3, to: 8 });
|
50
|
+
expect(text + "").toEqual("helrld");
|
51
|
+
});
|
52
|
+
});
|
53
|
+
|
54
|
+
describe("Position operations", () => {
|
55
|
+
test("idxBefore returns index before a position", () => {
|
56
|
+
const text = CoPlainText.create("hello world", { owner: me });
|
57
|
+
|
58
|
+
// Get position at index 5 (between "hello" and " world")
|
59
|
+
const pos = text.posBefore(5);
|
60
|
+
expect(pos).toBeDefined();
|
61
|
+
|
62
|
+
// Verify idxBefore returns the index before the position (4)
|
63
|
+
// This makes sense as the position is between characters,
|
64
|
+
// and idxBefore returns the index of the last character before that position
|
65
|
+
const idx = text.idxBefore(pos!);
|
66
|
+
expect(idx).toBe(4); // Index of 'o' in "hello"
|
67
|
+
});
|
68
|
+
|
69
|
+
test("idxAfter returns index after a position", () => {
|
70
|
+
const text = CoPlainText.create("hello world", { owner: me });
|
71
|
+
|
72
|
+
// Get position at index 5 (between "hello" and " world")
|
73
|
+
const pos = text.posBefore(5);
|
74
|
+
expect(pos).toBeDefined();
|
75
|
+
|
76
|
+
// Verify idxAfter returns the index after the position (5)
|
77
|
+
// This makes sense as the position is between characters,
|
78
|
+
// and idxAfter returns the index of the first character after that position
|
79
|
+
const idx = text.idxAfter(pos!);
|
80
|
+
expect(idx).toBe(5); // Index of ' ' in "hello world"
|
81
|
+
});
|
82
|
+
});
|
83
|
+
});
|
84
|
+
|
85
|
+
describe("Loading and availability", () => {
|
86
|
+
test("can load text across peers", async () => {
|
87
|
+
const { me, text } = await initNodeAndText();
|
88
|
+
const id = text.id;
|
89
|
+
|
90
|
+
// Set up peer connections
|
91
|
+
const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
|
92
|
+
peer1role: "server",
|
93
|
+
peer2role: "client",
|
94
|
+
});
|
95
|
+
|
96
|
+
if (!isControlledAccount(me)) {
|
97
|
+
throw "me is not a controlled account";
|
98
|
+
}
|
99
|
+
me._raw.core.node.syncManager.addPeer(secondPeer);
|
100
|
+
const { account: meOnSecondPeer } = await createJazzContext({
|
101
|
+
auth: fixedCredentialsAuth({
|
102
|
+
accountID: me.id,
|
103
|
+
secret: me._raw.agentSecret,
|
104
|
+
}),
|
105
|
+
sessionProvider: randomSessionProvider,
|
106
|
+
peersToLoadFrom: [initialAsPeer],
|
107
|
+
crypto: Crypto,
|
108
|
+
});
|
109
|
+
|
110
|
+
// Load the text on the second peer
|
111
|
+
const loaded = await CoPlainText.load(id, meOnSecondPeer);
|
112
|
+
expect(loaded).toBeDefined();
|
113
|
+
expect(loaded!.toString()).toBe("hello world");
|
114
|
+
});
|
115
|
+
});
|
116
|
+
|
117
|
+
test("Subscription & auto-resolution", async () => {
|
118
|
+
const { me, text } = await initNodeAndText();
|
119
|
+
|
120
|
+
// Set up peer connections
|
121
|
+
const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
|
122
|
+
peer1role: "server",
|
123
|
+
peer2role: "client",
|
124
|
+
});
|
125
|
+
|
126
|
+
if (!isControlledAccount(me)) {
|
127
|
+
throw "me is not a controlled account";
|
128
|
+
}
|
129
|
+
me._raw.core.node.syncManager.addPeer(secondPeer);
|
130
|
+
const { account: meOnSecondPeer } = await createJazzContext({
|
131
|
+
auth: fixedCredentialsAuth({
|
132
|
+
accountID: me.id,
|
133
|
+
secret: me._raw.agentSecret,
|
134
|
+
}),
|
135
|
+
sessionProvider: randomSessionProvider,
|
136
|
+
peersToLoadFrom: [initialAsPeer],
|
137
|
+
crypto: Crypto,
|
138
|
+
});
|
139
|
+
|
140
|
+
const queue = new cojsonInternals.Channel();
|
141
|
+
|
142
|
+
// Subscribe to text updates
|
143
|
+
CoPlainText.subscribe(text.id, meOnSecondPeer, (subscribedText) => {
|
144
|
+
void queue.push(subscribedText);
|
145
|
+
});
|
146
|
+
|
147
|
+
// Initial subscription should give us the text
|
148
|
+
const update1 = (await queue.next()).value;
|
149
|
+
expect(update1.toString()).toBe("hello world");
|
150
|
+
|
151
|
+
// When we make a change, we should get an update
|
152
|
+
text.insertAfter(5, " beautiful");
|
153
|
+
const update2 = (await queue.next()).value;
|
154
|
+
expect(update2.toString()).toBe("hello beautiful world");
|
155
|
+
|
156
|
+
// When we make another change, we should get another update
|
157
|
+
update2.deleteRange({ from: 5, to: 15 }); // Delete " beautiful"
|
158
|
+
const update3 = (await queue.next()).value;
|
159
|
+
expect(update3.toString()).toBe("hello world");
|
160
|
+
});
|
161
|
+
});
|