cojson 0.6.6 → 0.7.0-alpha.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 (56) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/.turbo/turbo-build.log +35 -0
  3. package/CHANGELOG.md +6 -0
  4. package/dist/coValue.js.map +1 -1
  5. package/dist/coValueCore.js.map +1 -1
  6. package/dist/coValues/account.js +5 -5
  7. package/dist/coValues/account.js.map +1 -1
  8. package/dist/coValues/coList.js +39 -58
  9. package/dist/coValues/coList.js.map +1 -1
  10. package/dist/coValues/coMap.js +20 -61
  11. package/dist/coValues/coMap.js.map +1 -1
  12. package/dist/coValues/coStream.js +14 -64
  13. package/dist/coValues/coStream.js.map +1 -1
  14. package/dist/coValues/group.js +57 -59
  15. package/dist/coValues/group.js.map +1 -1
  16. package/dist/coreToCoValue.js +17 -12
  17. package/dist/coreToCoValue.js.map +1 -1
  18. package/dist/index.js +8 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/localNode.js +54 -38
  21. package/dist/localNode.js.map +1 -1
  22. package/dist/permissions.js +2 -2
  23. package/dist/permissions.js.map +1 -1
  24. package/dist/sync.js +3 -3
  25. package/dist/sync.js.map +1 -1
  26. package/dist/tests/testUtils.js +6 -11
  27. package/dist/tests/testUtils.js.map +1 -1
  28. package/dist/typeUtils/expectGroup.js +2 -2
  29. package/dist/typeUtils/expectGroup.js.map +1 -1
  30. package/dist/typeUtils/isCoValue.js +8 -8
  31. package/dist/typeUtils/isCoValue.js.map +1 -1
  32. package/package.json +52 -53
  33. package/src/coValue.ts +21 -21
  34. package/src/coValueCore.ts +8 -8
  35. package/src/coValues/account.ts +14 -26
  36. package/src/coValues/coList.ts +58 -97
  37. package/src/coValues/coMap.ts +27 -129
  38. package/src/coValues/coStream.ts +31 -137
  39. package/src/coValues/group.ts +52 -46
  40. package/src/coreToCoValue.ts +16 -12
  41. package/src/index.ts +27 -36
  42. package/src/jsonValue.ts +1 -1
  43. package/src/localNode.ts +88 -75
  44. package/src/media.ts +4 -4
  45. package/src/permissions.ts +2 -2
  46. package/src/sync.ts +3 -3
  47. package/src/tests/account.test.ts +12 -12
  48. package/src/tests/coValue.test.ts +149 -182
  49. package/src/tests/coValueCore.test.ts +8 -3
  50. package/src/tests/crypto.test.ts +7 -0
  51. package/src/tests/group.test.ts +13 -6
  52. package/src/tests/permissions.test.ts +648 -840
  53. package/src/tests/sync.test.ts +44 -68
  54. package/src/tests/testUtils.ts +6 -11
  55. package/src/typeUtils/expectGroup.ts +4 -4
  56. package/src/typeUtils/isCoValue.ts +11 -11
package/src/localNode.ts CHANGED
@@ -16,25 +16,25 @@ import {
16
16
  } from "./coValueCore.js";
17
17
  import {
18
18
  InviteSecret,
19
- Group,
19
+ RawGroup,
20
20
  secretSeedFromInviteSecret,
21
21
  } from "./coValues/group.js";
22
22
  import { Peer, PeerID, SyncManager } from "./sync.js";
23
23
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
24
24
  import { CoID } from "./coValue.js";
25
25
  import {
26
- Account,
26
+ RawAccount,
27
27
  AccountMeta,
28
28
  accountHeaderForInitialAgentSecret,
29
29
  ControlledAccountOrAgent,
30
- ControlledAccount,
30
+ RawControlledAccount,
31
31
  ControlledAgent,
32
32
  AccountID,
33
33
  Profile,
34
- AccountMigration,
34
+ RawAccountMigration,
35
35
  } from "./coValues/account.js";
36
- import { CoMap } from "./coValues/coMap.js";
37
- import { CoValue } from "./index.js";
36
+ import { RawCoMap } from "./coValues/coMap.js";
37
+ import { RawCoValue } from "./index.js";
38
38
  import { expectGroup } from "./typeUtils/expectGroup.js";
39
39
 
40
40
  /** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
@@ -69,8 +69,6 @@ export class LocalNode {
69
69
 
70
70
  /** @category 2. Node Creation */
71
71
  static async withNewlyCreatedAccount<
72
- P extends Profile = Profile,
73
- R extends CoMap = CoMap,
74
72
  Meta extends AccountMeta = AccountMeta
75
73
  >({
76
74
  name,
@@ -80,7 +78,7 @@ export class LocalNode {
80
78
  }: {
81
79
  name: string;
82
80
  peersToLoadFrom?: Peer[];
83
- migration?: AccountMigration<P, R, Meta>;
81
+ migration?: RawAccountMigration<Meta>;
84
82
  initialAgentSecret?: AgentSecret;
85
83
  }): Promise<{
86
84
  node: LocalNode;
@@ -102,7 +100,7 @@ export class LocalNode {
102
100
  );
103
101
 
104
102
  const accountOnNodeWithAccount =
105
- nodeWithAccount.account as ControlledAccount<P, R, Meta>;
103
+ nodeWithAccount.account as RawControlledAccount<Meta>;
106
104
 
107
105
  const profile = nodeWithAccount.expectProfileLoaded(
108
106
  accountOnNodeWithAccount.id,
@@ -116,19 +114,30 @@ export class LocalNode {
116
114
  }
117
115
 
118
116
  if (migration) {
119
- await migration(accountOnNodeWithAccount, profile as P, nodeWithAccount);
117
+ await migration(accountOnNodeWithAccount, profile, nodeWithAccount);
120
118
  }
121
119
 
122
- nodeWithAccount.account = new ControlledAccount(
120
+ const controlledAccount = new RawControlledAccount(
123
121
  accountOnNodeWithAccount.core,
124
122
  accountOnNodeWithAccount.agentSecret
125
123
  );
126
124
 
125
+ nodeWithAccount.account = controlledAccount
126
+ nodeWithAccount.coValues[controlledAccount.id] = {
127
+ state: "loaded",
128
+ coValue: controlledAccount.core,
129
+ };
130
+ controlledAccount.core._cachedContent = undefined;
131
+
127
132
  // we shouldn't need this, but it fixes account data not syncing for new accounts
128
133
  function syncAllCoValuesAfterCreateAccount() {
129
- for (const coValueEntry of Object.values(nodeWithAccount.coValues)) {
134
+ for (const coValueEntry of Object.values(
135
+ nodeWithAccount.coValues
136
+ )) {
130
137
  if (coValueEntry.state === "loaded") {
131
- void nodeWithAccount.syncManager.syncCoValue(coValueEntry.coValue);
138
+ void nodeWithAccount.syncManager.syncCoValue(
139
+ coValueEntry.coValue
140
+ );
132
141
  }
133
142
  }
134
143
  }
@@ -146,11 +155,7 @@ export class LocalNode {
146
155
  }
147
156
 
148
157
  /** @category 2. Node Creation */
149
- static async withLoadedAccount<
150
- P extends Profile = Profile,
151
- R extends CoMap = CoMap,
152
- Meta extends AccountMeta = AccountMeta
153
- >({
158
+ static async withLoadedAccount<Meta extends AccountMeta = AccountMeta>({
154
159
  accountID,
155
160
  accountSecret,
156
161
  sessionID,
@@ -161,7 +166,7 @@ export class LocalNode {
161
166
  accountSecret: AgentSecret;
162
167
  sessionID: SessionID;
163
168
  peersToLoadFrom: Peer[];
164
- migration?: AccountMigration<P, R, Meta>;
169
+ migration?: RawAccountMigration<Meta>;
165
170
  }): Promise<LocalNode> {
166
171
  const loadingNode = new LocalNode(
167
172
  new ControlledAgent(accountSecret),
@@ -180,7 +185,7 @@ export class LocalNode {
180
185
  throw new Error("Account unavailable from all peers");
181
186
  }
182
187
 
183
- const controlledAccount = new ControlledAccount(
188
+ const controlledAccount = new RawControlledAccount(
184
189
  account.core,
185
190
  accountSecret
186
191
  );
@@ -198,6 +203,7 @@ export class LocalNode {
198
203
  state: "loaded",
199
204
  coValue: controlledAccount.core,
200
205
  };
206
+ controlledAccount.core._cachedContent = undefined;
201
207
 
202
208
  const profileID = account.get("profile");
203
209
  if (!profileID) {
@@ -205,13 +211,17 @@ export class LocalNode {
205
211
  }
206
212
  const profile = await node.load(profileID);
207
213
 
214
+ if (profile === "unavailable") {
215
+ throw new Error("Profile unavailable from all peers");
216
+ }
217
+
208
218
  if (migration) {
209
219
  await migration(
210
- controlledAccount as ControlledAccount<P, R, Meta>,
211
- profile as P,
220
+ controlledAccount as RawControlledAccount<Meta>,
221
+ profile,
212
222
  node
213
223
  );
214
- node.account = new ControlledAccount(
224
+ node.account = new RawControlledAccount(
215
225
  controlledAccount.core,
216
226
  controlledAccount.agentSecret
217
227
  );
@@ -275,7 +285,7 @@ export class LocalNode {
275
285
  *
276
286
  * @category 3. Low-level
277
287
  */
278
- async load<T extends CoValue>(
288
+ async load<T extends RawCoValue>(
279
289
  id: CoID<T>,
280
290
  onProgress?: (progress: number) => void
281
291
  ): Promise<T | "unavailable"> {
@@ -288,8 +298,19 @@ export class LocalNode {
288
298
  return core.getCurrentContent() as T;
289
299
  }
290
300
 
301
+ getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
302
+ const entry = this.coValues[id];
303
+ if (!entry) {
304
+ return undefined;
305
+ }
306
+ if (entry.state === "loaded") {
307
+ return entry.coValue.getCurrentContent() as T;
308
+ }
309
+ return undefined;
310
+ }
311
+
291
312
  /** @category 3. Low-level */
292
- subscribe<T extends CoValue>(
313
+ subscribe<T extends RawCoValue>(
293
314
  id: CoID<T>,
294
315
  callback: (update: T | "unavailable") => void
295
316
  ): () => void {
@@ -321,7 +342,7 @@ export class LocalNode {
321
342
  }
322
343
 
323
344
  /** @deprecated Use Account.acceptInvite instead */
324
- async acceptInvite<T extends CoValue>(
345
+ async acceptInvite<T extends RawCoValue>(
325
346
  groupOrOwnedValueID: CoID<T>,
326
347
  inviteSecret: InviteSecret
327
348
  ): Promise<void> {
@@ -335,7 +356,7 @@ export class LocalNode {
335
356
 
336
357
  if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
337
358
  return this.acceptInvite(
338
- groupOrOwnedValue.core.header.ruleset.group as CoID<Group>,
359
+ groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
339
360
  inviteSecret
340
361
  );
341
362
  } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
@@ -447,7 +468,7 @@ export class LocalNode {
447
468
  createAccount(
448
469
  name: string,
449
470
  agentSecret = newRandomAgentSecret()
450
- ): ControlledAccount {
471
+ ): RawControlledAccount {
451
472
  const accountAgentID = getAgentID(agentSecret);
452
473
  let account = expectGroup(
453
474
  this.createCoValue(accountHeaderForInitialAgentSecret(agentSecret))
@@ -458,29 +479,23 @@ export class LocalNode {
458
479
  .getCurrentContent()
459
480
  );
460
481
 
461
- account = account.mutate((editable) => {
462
- editable.set(accountAgentID, "admin", "trusting");
482
+ account.set(accountAgentID, "admin", "trusting");
463
483
 
464
- const readKey = newRandomKeySecret();
484
+ const readKey = newRandomKeySecret();
465
485
 
466
- const sealed = seal({
467
- message: readKey.secret,
468
- from: getAgentSealerSecret(agentSecret),
469
- to: getAgentSealerID(accountAgentID),
470
- nOnceMaterial: {
471
- in: account.id,
472
- tx: account.core.nextTransactionID(),
473
- },
474
- });
486
+ const sealed = seal({
487
+ message: readKey.secret,
488
+ from: getAgentSealerSecret(agentSecret),
489
+ to: getAgentSealerID(accountAgentID),
490
+ nOnceMaterial: {
491
+ in: account.id,
492
+ tx: account.core.nextTransactionID(),
493
+ },
494
+ });
475
495
 
476
- editable.set(
477
- `${readKey.id}_for_${accountAgentID}`,
478
- sealed,
479
- "trusting"
480
- );
496
+ account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
481
497
 
482
- editable.set("readKey", readKey.id, "trusting");
483
- });
498
+ account.set("readKey", readKey.id, "trusting");
484
499
 
485
500
  const profile = account.createMap<Profile>(
486
501
  { name },
@@ -490,7 +505,7 @@ export class LocalNode {
490
505
  "trusting"
491
506
  );
492
507
 
493
- account = account.set("profile", profile.id, "trusting");
508
+ account.set("profile", profile.id, "trusting");
494
509
 
495
510
  const accountOnThisNode = this.expectCoValueLoaded(account.id);
496
511
 
@@ -503,7 +518,7 @@ export class LocalNode {
503
518
  profileOnThisNode._sessionLogs = new Map(profile.core.sessionLogs);
504
519
  profileOnThisNode._cachedContent = undefined;
505
520
 
506
- return new ControlledAccount(accountOnThisNode, agentSecret);
521
+ return new RawControlledAccount(accountOnThisNode, agentSecret);
507
522
  }
508
523
 
509
524
  /** @internal */
@@ -531,7 +546,7 @@ export class LocalNode {
531
546
  );
532
547
  }
533
548
 
534
- return new Account(coValue).currentAgentID();
549
+ return new RawAccount(coValue).currentAgentID();
535
550
  }
536
551
 
537
552
  async resolveAccountAgentAsync(
@@ -566,13 +581,13 @@ export class LocalNode {
566
581
  );
567
582
  }
568
583
 
569
- return new Account(coValue).currentAgentID();
584
+ return new RawAccount(coValue).currentAgentID();
570
585
  }
571
586
 
572
587
  /**
573
588
  * @deprecated use Account.createGroup() instead
574
589
  */
575
- createGroup(): Group {
590
+ createGroup(): RawGroup {
576
591
  const groupCoValue = this.createCoValue({
577
592
  type: "comap",
578
593
  ruleset: { type: "group", initialAdmin: this.account.id },
@@ -582,27 +597,25 @@ export class LocalNode {
582
597
 
583
598
  let group = expectGroup(groupCoValue.getCurrentContent());
584
599
 
585
- group = group.mutate((editable) => {
586
- editable.set(this.account.id, "admin", "trusting");
587
-
588
- const readKey = newRandomKeySecret();
589
-
590
- editable.set(
591
- `${readKey.id}_for_${this.account.id}`,
592
- seal({
593
- message: readKey.secret,
594
- from: this.account.currentSealerSecret(),
595
- to: this.account.currentSealerID(),
596
- nOnceMaterial: {
597
- in: groupCoValue.id,
598
- tx: groupCoValue.nextTransactionID(),
599
- },
600
- }),
601
- "trusting"
602
- );
600
+ group.set(this.account.id, "admin", "trusting");
603
601
 
604
- editable.set("readKey", readKey.id, "trusting");
605
- });
602
+ const readKey = newRandomKeySecret();
603
+
604
+ group.set(
605
+ `${readKey.id}_for_${this.account.id}`,
606
+ seal({
607
+ message: readKey.secret,
608
+ from: this.account.currentSealerSecret(),
609
+ to: this.account.currentSealerID(),
610
+ nOnceMaterial: {
611
+ in: groupCoValue.id,
612
+ tx: groupCoValue.nextTransactionID(),
613
+ },
614
+ }),
615
+ "trusting"
616
+ );
617
+
618
+ group.set("readKey", readKey.id, "trusting");
606
619
 
607
620
  return group;
608
621
  }
@@ -649,9 +662,9 @@ export class LocalNode {
649
662
  }
650
663
  }
651
664
 
652
- if (account instanceof ControlledAccount) {
665
+ if (account instanceof RawControlledAccount) {
653
666
  // To make sure that when we edit the account, we're modifying the correct sessions
654
- const accountInNode = new ControlledAccount(
667
+ const accountInNode = new RawControlledAccount(
655
668
  newNode.expectCoValueLoaded(account.id),
656
669
  account.agentSecret
657
670
  );
package/src/media.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { CoMap } from './coValues/coMap.js'
2
- import { BinaryCoStream } from './coValues/coStream.js'
1
+ import { RawCoMap } from './coValues/coMap.js'
2
+ import { RawBinaryCoStream } from './coValues/coStream.js'
3
3
 
4
- export type ImageDefinition = CoMap<{
4
+ export type ImageDefinition = RawCoMap<{
5
5
  originalSize: [number, number];
6
6
  placeholderDataURL?: string;
7
- [res: `${number}x${number}`]: BinaryCoStream["id"];
7
+ [res: `${number}x${number}`]: RawBinaryCoStream["id"];
8
8
  }>;
@@ -5,7 +5,7 @@ import { KeyID } from "./crypto.js";
5
5
  import { CoValueCore, Transaction } from "./coValueCore.js";
6
6
  import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
7
7
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
8
- import { Account, AccountID, Profile } from "./coValues/account.js";
8
+ import { RawAccount, AccountID, Profile } from "./coValues/account.js";
9
9
  import { parseJSON } from "./jsonStringify.js";
10
10
  import { EVERYONE, Everyone } from "./coValues/group.js";
11
11
  import { expectGroup } from "./typeUtils/expectGroup.js";
@@ -248,7 +248,7 @@ export function determineValidTransactions(
248
248
  const groupAtTime = groupContent.atTime(tx.madeAt);
249
249
  const effectiveTransactor =
250
250
  transactor === groupContent.id &&
251
- groupAtTime instanceof Account
251
+ groupAtTime instanceof RawAccount
252
252
  ? groupAtTime.currentAgentID()
253
253
  : transactor;
254
254
  const transactorRoleAtTxTime =
package/src/sync.ts CHANGED
@@ -690,9 +690,9 @@ export class SyncManager {
690
690
  const done = new Promise<void>((resolve) => {
691
691
  setTimeout(async () => {
692
692
  delete this.requestedSyncs[coValue.id];
693
- if (entry.nRequestsThisTick >= 2) {
694
- console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
695
- }
693
+ // if (entry.nRequestsThisTick >= 2) {
694
+ // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
695
+ // }
696
696
  await this.actuallySyncCoValue(coValue);
697
697
  resolve();
698
698
  }, 0);
@@ -1,8 +1,15 @@
1
+ import { expect, test, beforeEach } from "vitest";
1
2
  import { newRandomSessionID } from "../coValueCore.js";
2
3
  import { cojsonReady } from "../index.js";
3
4
  import { LocalNode } from "../localNode.js";
4
5
  import { connectedPeers } from "../streamUtils.js";
5
6
 
7
+ import { webcrypto } from "node:crypto";
8
+ if (!("crypto" in globalThis)) {
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ (globalThis as any).crypto = webcrypto;
11
+ }
12
+
6
13
  beforeEach(async () => {
7
14
  await cojsonReady;
8
15
  });
@@ -29,14 +36,9 @@ test("A node with an account can create groups and and objects within them", asy
29
36
  const group = await node.createGroup();
30
37
  expect(group).not.toBeNull();
31
38
 
32
- let map = group.createMap();
33
- map = map.edit((edit) => {
34
- edit.set("foo", "bar", "private");
35
- expect(edit.get("foo")).toEqual("bar");
36
- });
37
-
39
+ const map = group.createMap();
40
+ map.set("foo", "bar", "private");
38
41
  expect(map.get("foo")).toEqual("bar");
39
-
40
42
  expect(map.lastEditAt("foo")?.by).toEqual(accountID);
41
43
  });
42
44
 
@@ -47,11 +49,9 @@ test("Can create account with one node, and then load it on another", async () =
47
49
  const group = await node.createGroup();
48
50
  expect(group).not.toBeNull();
49
51
 
50
- let map = group.createMap();
51
- map = map.edit((edit) => {
52
- edit.set("foo", "bar", "private");
53
- expect(edit.get("foo")).toEqual("bar");
54
- });
52
+ const map = group.createMap();
53
+ map.set("foo", "bar", "private");
54
+ expect(map.get("foo")).toEqual("bar");
55
55
 
56
56
  const [node1asPeer, node2asPeer] = connectedPeers("node1", "node2", {
57
57
  trace: true,