cojson 0.7.0-alpha.7 → 0.7.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 (113) hide show
  1. package/.eslintrc.cjs +3 -2
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -30
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +1106 -0
  6. package/CHANGELOG.md +98 -0
  7. package/README.md +3 -1
  8. package/dist/base64url.test.js +25 -0
  9. package/dist/base64url.test.js.map +1 -0
  10. package/dist/coValueCore.js +60 -37
  11. package/dist/coValueCore.js.map +1 -1
  12. package/dist/coValues/account.js +16 -15
  13. package/dist/coValues/account.js.map +1 -1
  14. package/dist/coValues/coList.js +1 -1
  15. package/dist/coValues/coList.js.map +1 -1
  16. package/dist/coValues/coMap.js +17 -8
  17. package/dist/coValues/coMap.js.map +1 -1
  18. package/dist/coValues/group.js +13 -14
  19. package/dist/coValues/group.js.map +1 -1
  20. package/dist/coreToCoValue.js.map +1 -1
  21. package/dist/crypto/PureJSCrypto.js +89 -0
  22. package/dist/crypto/PureJSCrypto.js.map +1 -0
  23. package/dist/crypto/WasmCrypto.js +127 -0
  24. package/dist/crypto/WasmCrypto.js.map +1 -0
  25. package/dist/crypto/crypto.js +151 -0
  26. package/dist/crypto/crypto.js.map +1 -0
  27. package/dist/ids.js +4 -2
  28. package/dist/ids.js.map +1 -1
  29. package/dist/index.js +6 -8
  30. package/dist/index.js.map +1 -1
  31. package/dist/jsonStringify.js.map +1 -1
  32. package/dist/localNode.js +41 -38
  33. package/dist/localNode.js.map +1 -1
  34. package/dist/permissions.js +6 -6
  35. package/dist/permissions.js.map +1 -1
  36. package/dist/storage/FileSystem.js +61 -0
  37. package/dist/storage/FileSystem.js.map +1 -0
  38. package/dist/storage/chunksAndKnownStates.js +97 -0
  39. package/dist/storage/chunksAndKnownStates.js.map +1 -0
  40. package/dist/storage/index.js +265 -0
  41. package/dist/storage/index.js.map +1 -0
  42. package/dist/sync.js +29 -25
  43. package/dist/sync.js.map +1 -1
  44. package/dist/tests/account.test.js +58 -0
  45. package/dist/tests/account.test.js.map +1 -0
  46. package/dist/tests/coList.test.js +76 -0
  47. package/dist/tests/coList.test.js.map +1 -0
  48. package/dist/tests/coMap.test.js +136 -0
  49. package/dist/tests/coMap.test.js.map +1 -0
  50. package/dist/tests/coStream.test.js +172 -0
  51. package/dist/tests/coStream.test.js.map +1 -0
  52. package/dist/tests/coValueCore.test.js +114 -0
  53. package/dist/tests/coValueCore.test.js.map +1 -0
  54. package/dist/tests/crypto.test.js +118 -0
  55. package/dist/tests/crypto.test.js.map +1 -0
  56. package/dist/tests/cryptoImpl.test.js +113 -0
  57. package/dist/tests/cryptoImpl.test.js.map +1 -0
  58. package/dist/tests/group.test.js +34 -0
  59. package/dist/tests/group.test.js.map +1 -0
  60. package/dist/tests/permissions.test.js +1060 -0
  61. package/dist/tests/permissions.test.js.map +1 -0
  62. package/dist/tests/sync.test.js +816 -0
  63. package/dist/tests/sync.test.js.map +1 -0
  64. package/dist/tests/testUtils.js +12 -11
  65. package/dist/tests/testUtils.js.map +1 -1
  66. package/dist/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  67. package/dist/typeUtils/isAccountID.js.map +1 -1
  68. package/dist/typeUtils/isCoValue.js.map +1 -1
  69. package/package.json +14 -27
  70. package/src/base64url.test.ts +6 -5
  71. package/src/coValue.ts +1 -1
  72. package/src/coValueCore.ts +179 -126
  73. package/src/coValues/account.ts +30 -32
  74. package/src/coValues/coList.ts +11 -11
  75. package/src/coValues/coMap.ts +27 -17
  76. package/src/coValues/coStream.ts +17 -17
  77. package/src/coValues/group.ts +93 -109
  78. package/src/coreToCoValue.ts +5 -2
  79. package/src/crypto/PureJSCrypto.ts +200 -0
  80. package/src/crypto/WasmCrypto.ts +259 -0
  81. package/src/crypto/crypto.ts +336 -0
  82. package/src/ids.ts +8 -7
  83. package/src/index.ts +24 -24
  84. package/src/jsonStringify.ts +6 -4
  85. package/src/jsonValue.ts +2 -2
  86. package/src/localNode.ts +103 -109
  87. package/src/media.ts +3 -3
  88. package/src/permissions.ts +19 -21
  89. package/src/storage/FileSystem.ts +152 -0
  90. package/src/storage/chunksAndKnownStates.ts +139 -0
  91. package/src/storage/index.ts +479 -0
  92. package/src/streamUtils.ts +12 -12
  93. package/src/sync.ts +79 -63
  94. package/src/tests/account.test.ts +15 -15
  95. package/src/tests/coList.test.ts +94 -0
  96. package/src/tests/coMap.test.ts +162 -0
  97. package/src/tests/coStream.test.ts +246 -0
  98. package/src/tests/coValueCore.test.ts +36 -37
  99. package/src/tests/crypto.test.ts +66 -72
  100. package/src/tests/cryptoImpl.test.ts +183 -0
  101. package/src/tests/group.test.ts +16 -17
  102. package/src/tests/permissions.test.ts +269 -283
  103. package/src/tests/sync.test.ts +122 -123
  104. package/src/tests/testUtils.ts +24 -21
  105. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +1 -2
  106. package/src/typeUtils/expectGroup.ts +1 -1
  107. package/src/typeUtils/isAccountID.ts +0 -1
  108. package/src/typeUtils/isCoValue.ts +1 -2
  109. package/tsconfig.json +0 -1
  110. package/dist/crypto.js +0 -254
  111. package/dist/crypto.js.map +0 -1
  112. package/src/crypto.ts +0 -484
  113. package/src/tests/coValue.test.ts +0 -497
package/src/sync.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Signature } from "./crypto.js";
1
+ import { Signature } from "./crypto/crypto.js";
2
2
  import { CoValueHeader, Transaction } from "./coValueCore.js";
3
3
  import { CoValueCore } from "./coValueCore.js";
4
4
  import { LocalNode } from "./localNode.js";
@@ -82,7 +82,7 @@ export interface PeerState {
82
82
 
83
83
  export function combinedKnownStates(
84
84
  stateA: CoValueKnownState,
85
- stateB: CoValueKnownState
85
+ stateB: CoValueKnownState,
86
86
  ): CoValueKnownState {
87
87
  const sessionStates: CoValueKnownState["sessions"] = {};
88
88
 
@@ -108,7 +108,11 @@ export function combinedKnownStates(
108
108
  export class SyncManager {
109
109
  peers: { [key: PeerID]: PeerState } = {};
110
110
  local: LocalNode;
111
- requestedSyncs: { [id: RawCoID]: {done: Promise<void>, nRequestsThisTick: number} | undefined } = {};
111
+ requestedSyncs: {
112
+ [id: RawCoID]:
113
+ | { done: Promise<void>; nRequestsThisTick: number }
114
+ | undefined;
115
+ } = {};
112
116
 
113
117
  constructor(local: LocalNode) {
114
118
  this.local = local;
@@ -151,31 +155,31 @@ export class SyncManager {
151
155
  throw new Error("Expected firstPeerState to be waiting " + id);
152
156
  }
153
157
  await new Promise<void>((resolve) => {
154
- const timeout = setTimeout(() => {
155
- if (this.local.coValues[id]?.state === "loading") {
156
- // console.warn(
157
- // "Timeout waiting for peer to load",
158
- // id,
159
- // "from",
160
- // peer.id,
161
- // "and it hasn't loaded from other peers yet"
162
- // );
163
- }
164
- resolve();
165
- }, 1000);
158
+ // const timeout = setTimeout(() => {
159
+ // if (this.local.coValues[id]?.state === "loading") {
160
+ // console.warn(
161
+ // "Timeout waiting for peer to load",
162
+ // id,
163
+ // "from",
164
+ // peer.id,
165
+ // "and it hasn't loaded from other peers yet"
166
+ // );
167
+ // }
168
+ // resolve();
169
+ // }, 1000);
166
170
  firstStateEntry.done
167
171
  .then(() => {
168
- clearTimeout(timeout);
172
+ // clearTimeout(timeout);
169
173
  resolve();
170
174
  })
171
175
  .catch((e) => {
172
- clearTimeout(timeout);
176
+ // clearTimeout(timeout);
173
177
  console.error(
174
178
  "Error waiting for peer to load",
175
179
  id,
176
180
  "from",
177
181
  peer.id,
178
- e
182
+ e,
179
183
  );
180
184
  resolve();
181
185
  });
@@ -203,7 +207,7 @@ export class SyncManager {
203
207
  throw new Error(
204
208
  `Unknown message type ${
205
209
  (msg as { action: "string" }).action
206
- }`
210
+ }`,
207
211
  );
208
212
  }
209
213
  }
@@ -243,15 +247,21 @@ export class SyncManager {
243
247
  async tellUntoldKnownStateIncludingDependencies(
244
248
  id: RawCoID,
245
249
  peer: PeerState,
246
- asDependencyOf?: RawCoID
250
+ asDependencyOf?: RawCoID,
247
251
  ) {
248
252
  const coValue = this.local.expectCoValueLoaded(id);
249
253
 
250
- await Promise.all(coValue.getDependedOnCoValues().map(dependentCoID => this.tellUntoldKnownStateIncludingDependencies(
251
- dependentCoID,
252
- peer,
253
- asDependencyOf || id
254
- )));
254
+ await Promise.all(
255
+ coValue
256
+ .getDependedOnCoValues()
257
+ .map((dependentCoID) =>
258
+ this.tellUntoldKnownStateIncludingDependencies(
259
+ dependentCoID,
260
+ peer,
261
+ asDependencyOf || id,
262
+ ),
263
+ ),
264
+ );
255
265
 
256
266
  if (!peer.toldKnownState.has(id)) {
257
267
  await this.trySendToPeer(peer, {
@@ -267,10 +277,16 @@ export class SyncManager {
267
277
  async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
268
278
  const coValue = this.local.expectCoValueLoaded(id);
269
279
 
270
- await Promise.all(coValue.getDependedOnCoValues().map(id => this.sendNewContentIncludingDependencies(id, peer)));
280
+ await Promise.all(
281
+ coValue
282
+ .getDependedOnCoValues()
283
+ .map((id) =>
284
+ this.sendNewContentIncludingDependencies(id, peer),
285
+ ),
286
+ );
271
287
 
272
288
  const newContentPieces = coValue.newContentSince(
273
- peer.optimisticKnownStates[id]
289
+ peer.optimisticKnownStates[id],
274
290
  );
275
291
 
276
292
  if (newContentPieces) {
@@ -302,7 +318,7 @@ export class SyncManager {
302
318
 
303
319
  peer.optimisticKnownStates[id] = combinedKnownStates(
304
320
  optimisticKnownStateBefore,
305
- coValue.knownState()
321
+ coValue.knownState(),
306
322
  );
307
323
  }
308
324
  }
@@ -323,7 +339,7 @@ export class SyncManager {
323
339
  if (peer.role === "server") {
324
340
  const initialSync = async () => {
325
341
  for (const id of Object.keys(
326
- this.local.coValues
342
+ this.local.coValues,
327
343
  ) as RawCoID[]) {
328
344
  // console.log("subscribing to after peer added", id, peer.id)
329
345
  await this.subscribeToIncludingDependencies(id, peerState);
@@ -350,9 +366,9 @@ export class SyncManager {
350
366
  JSON.stringify(msg, (k, v) =>
351
367
  k === "changes" || k === "encryptedChanges"
352
368
  ? v.slice(0, 20) + "..."
353
- : v
369
+ : v,
354
370
  ),
355
- e
371
+ e,
356
372
  );
357
373
  });
358
374
  // await new Promise<void>((resolve) => {
@@ -365,9 +381,9 @@ export class SyncManager {
365
381
  JSON.stringify(msg, (k, v) =>
366
382
  k === "changes" || k === "encryptedChanges"
367
383
  ? v.slice(0, 20) + "..."
368
- : v
384
+ : v,
369
385
  ),
370
- e
386
+ e,
371
387
  );
372
388
  if (peerState.delayOnError) {
373
389
  await new Promise<void>((resolve) => {
@@ -417,8 +433,8 @@ export class SyncManager {
417
433
  `Error writing to peer ${peer.id}, disconnecting`,
418
434
  {
419
435
  cause: e,
420
- }
421
- )
436
+ },
437
+ ),
422
438
  );
423
439
  delete this.peers[peer.id];
424
440
  });
@@ -470,7 +486,7 @@ export class SyncManager {
470
486
 
471
487
  peer.optimisticKnownStates[msg.id] = combinedKnownStates(
472
488
  peer.optimisticKnownStates[msg.id] || emptyKnownState(msg.id),
473
- knownStateIn(msg)
489
+ knownStateIn(msg),
474
490
  );
475
491
 
476
492
  if (!entry) {
@@ -481,18 +497,18 @@ export class SyncManager {
481
497
  .catch((e) => {
482
498
  console.error(
483
499
  `Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
484
- e
500
+ e,
485
501
  );
486
502
  });
487
503
  entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
488
504
  } else {
489
505
  throw new Error(
490
- "Expected coValue dependency entry to be created, missing subscribe?"
506
+ "Expected coValue dependency entry to be created, missing subscribe?",
491
507
  );
492
508
  }
493
509
  } else {
494
510
  throw new Error(
495
- "Expected coValue entry to be created, missing subscribe?"
511
+ "Expected coValue entry to be created, missing subscribe?",
496
512
  );
497
513
  }
498
514
  }
@@ -516,7 +532,7 @@ export class SyncManager {
516
532
  // );
517
533
  if (
518
534
  Object.values(entry.firstPeerState).every(
519
- (s) => s.type === "unavailable"
535
+ (s) => s.type === "unavailable",
520
536
  )
521
537
  ) {
522
538
  entry.resolve("unavailable");
@@ -533,7 +549,7 @@ export class SyncManager {
533
549
 
534
550
  if (!entry) {
535
551
  throw new Error(
536
- "Expected coValue entry to be created, missing subscribe?"
552
+ "Expected coValue entry to be created, missing subscribe?",
537
553
  );
538
554
  }
539
555
 
@@ -543,7 +559,7 @@ export class SyncManager {
543
559
 
544
560
  if (!peerOptimisticKnownState) {
545
561
  throw new Error(
546
- "Expected optimisticKnownState to be set for coValue we receive new content for"
562
+ "Expected optimisticKnownState to be set for coValue we receive new content for",
547
563
  );
548
564
  }
549
565
 
@@ -578,7 +594,7 @@ export class SyncManager {
578
594
  let invalidStateAssumed = false;
579
595
 
580
596
  for (const [sessionID, newContentForSession] of Object.entries(
581
- msg.new
597
+ msg.new,
582
598
  ) as [SessionID, SessionNewContent][]) {
583
599
  const ourKnownTxIdx =
584
600
  coValue.sessionLogs.get(sessionID)?.transactions.length;
@@ -605,7 +621,7 @@ export class SyncManager {
605
621
  sessionID,
606
622
  newTransactions,
607
623
  undefined,
608
- newContentForSession.lastSignature
624
+ newContentForSession.lastSignature,
609
625
  );
610
626
  const after = performance.now();
611
627
  if (after - before > 80) {
@@ -613,7 +629,7 @@ export class SyncManager {
613
629
  .map((t) =>
614
630
  t.privacy === "private"
615
631
  ? t.encryptedChanges.length
616
- : t.changes.length
632
+ : t.changes.length,
617
633
  )
618
634
  .reduce((a, b) => a + b, 0);
619
635
  console.log(
@@ -623,16 +639,16 @@ export class SyncManager {
623
639
  (1000 * totalTxLength) /
624
640
  (after - before) /
625
641
  (1024 * 1024)
626
- ).toFixed(2)} MB/s`
642
+ ).toFixed(2)} MB/s`,
627
643
  );
628
644
  }
629
645
 
630
646
  const theirTotalnTxs = Object.values(
631
- peer.optimisticKnownStates[msg.id]?.sessions || {}
647
+ peer.optimisticKnownStates[msg.id]?.sessions || {},
632
648
  ).reduce((sum, nTxs) => sum + nTxs, 0);
633
649
  const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
634
650
  (sum, session) => sum + session.transactions.length,
635
- 0
651
+ 0,
636
652
  );
637
653
 
638
654
  entry.onProgress?.(ourTotalnTxs / theirTotalnTxs);
@@ -644,8 +660,8 @@ export class SyncManager {
644
660
  JSON.stringify(newTransactions, (k, v) =>
645
661
  k === "changes" || k === "encryptedChanges"
646
662
  ? v.slice(0, 20) + "..."
647
- : v
648
- )
663
+ : v,
664
+ ),
649
665
  );
650
666
  continue;
651
667
  }
@@ -653,7 +669,7 @@ export class SyncManager {
653
669
  peerOptimisticKnownState.sessions[sessionID] = Math.max(
654
670
  peerOptimisticKnownState.sessions[sessionID] || 0,
655
671
  newContentForSession.after +
656
- newContentForSession.newTransactions.length
672
+ newContentForSession.newTransactions.length,
657
673
  );
658
674
  }
659
675
 
@@ -688,14 +704,14 @@ export class SyncManager {
688
704
  return this.requestedSyncs[coValue.id]!.done;
689
705
  } else {
690
706
  const done = new Promise<void>((resolve) => {
691
- setTimeout(async () => {
707
+ queueMicrotask(async () => {
692
708
  delete this.requestedSyncs[coValue.id];
693
709
  // if (entry.nRequestsThisTick >= 2) {
694
710
  // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
695
711
  // }
696
712
  await this.actuallySyncCoValue(coValue);
697
713
  resolve();
698
- }, 0);
714
+ });
699
715
  });
700
716
  const entry = {
701
717
  done,
@@ -707,30 +723,30 @@ export class SyncManager {
707
723
  }
708
724
 
709
725
  async actuallySyncCoValue(coValue: CoValueCore) {
710
- let blockingSince = performance.now();
726
+ // let blockingSince = performance.now();
711
727
  for (const peer of this.peersInPriorityOrder()) {
712
- if (performance.now() - blockingSince > 5) {
713
- await new Promise<void>((resolve) => {
714
- setTimeout(resolve, 0);
715
- });
716
- blockingSince = performance.now();
717
- }
728
+ // if (performance.now() - blockingSince > 5) {
729
+ // await new Promise<void>((resolve) => {
730
+ // setTimeout(resolve, 0);
731
+ // });
732
+ // blockingSince = performance.now();
733
+ // }
718
734
  const optimisticKnownState = peer.optimisticKnownStates[coValue.id];
719
735
 
720
736
  if (optimisticKnownState) {
721
737
  await this.tellUntoldKnownStateIncludingDependencies(
722
738
  coValue.id,
723
- peer
739
+ peer,
724
740
  );
725
741
  await this.sendNewContentIncludingDependencies(
726
742
  coValue.id,
727
- peer
743
+ peer,
728
744
  );
729
745
  } else if (peer.role === "server") {
730
746
  await this.subscribeToIncludingDependencies(coValue.id, peer);
731
747
  await this.sendNewContentIncludingDependencies(
732
748
  coValue.id,
733
- peer
749
+ peer,
734
750
  );
735
751
  }
736
752
  }
@@ -1,22 +1,17 @@
1
- import { expect, test, beforeEach } from "vitest";
1
+ import { expect, test } from "vitest";
2
2
  import { newRandomSessionID } from "../coValueCore.js";
3
- import { cojsonReady } from "../index.js";
4
3
  import { LocalNode } from "../localNode.js";
5
4
  import { connectedPeers } from "../streamUtils.js";
5
+ import { WasmCrypto } from "../crypto/WasmCrypto.js";
6
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
-
13
- beforeEach(async () => {
14
- await cojsonReady;
15
- });
7
+ const Crypto = await WasmCrypto.create();
16
8
 
17
9
  test("Can create a node while creating a new account with profile", async () => {
18
10
  const { node, accountID, accountSecret, sessionID } =
19
- await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
11
+ await LocalNode.withNewlyCreatedAccount({
12
+ creationProps: { name: "Hermes Puggington" },
13
+ crypto: Crypto,
14
+ });
20
15
 
21
16
  expect(node).not.toBeNull();
22
17
  expect(accountID).not.toBeNull();
@@ -24,13 +19,14 @@ test("Can create a node while creating a new account with profile", async () =>
24
19
  expect(sessionID).not.toBeNull();
25
20
 
26
21
  expect(node.expectProfileLoaded(accountID).get("name")).toEqual(
27
- "Hermes Puggington"
22
+ "Hermes Puggington",
28
23
  );
29
24
  });
30
25
 
31
26
  test("A node with an account can create groups and and objects within them", async () => {
32
27
  const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
33
- name: "Hermes Puggington",
28
+ creationProps: { name: "Hermes Puggington" },
29
+ crypto: Crypto,
34
30
  });
35
31
 
36
32
  const group = await node.createGroup();
@@ -44,7 +40,10 @@ test("A node with an account can create groups and and objects within them", asy
44
40
 
45
41
  test("Can create account with one node, and then load it on another", async () => {
46
42
  const { node, accountID, accountSecret } =
47
- await LocalNode.withNewlyCreatedAccount({ name: "Hermes Puggington" });
43
+ await LocalNode.withNewlyCreatedAccount({
44
+ creationProps: { name: "Hermes Puggington" },
45
+ crypto: Crypto,
46
+ });
48
47
 
49
48
  const group = await node.createGroup();
50
49
  expect(group).not.toBeNull();
@@ -66,6 +65,7 @@ test("Can create account with one node, and then load it on another", async () =
66
65
  accountSecret,
67
66
  sessionID: newRandomSessionID(accountID),
68
67
  peersToLoadFrom: [node1asPeer],
68
+ crypto: Crypto,
69
69
  });
70
70
 
71
71
  const map2 = await node2.load(map.id);
@@ -0,0 +1,94 @@
1
+ import { expect, test } from "vitest";
2
+ import { expectList } from "../coValue.js";
3
+ import { WasmCrypto } from "../index.js";
4
+ import { LocalNode } from "../localNode.js";
5
+ import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
6
+
7
+ const Crypto = await WasmCrypto.create();
8
+
9
+ test("Empty CoList works", () => {
10
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
11
+
12
+ const coValue = node.createCoValue({
13
+ type: "colist",
14
+ ruleset: { type: "unsafeAllowAll" },
15
+ meta: null,
16
+ ...Crypto.createdNowUnique(),
17
+ });
18
+
19
+ const content = expectList(coValue.getCurrentContent());
20
+
21
+ expect(content.type).toEqual("colist");
22
+ expect(content.toJSON()).toEqual([]);
23
+ });
24
+
25
+ test("Can append, prepend, delete and replace items in CoList", () => {
26
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
27
+
28
+ const coValue = node.createCoValue({
29
+ type: "colist",
30
+ ruleset: { type: "unsafeAllowAll" },
31
+ meta: null,
32
+ ...Crypto.createdNowUnique(),
33
+ });
34
+
35
+ const content = expectList(coValue.getCurrentContent());
36
+
37
+ content.append("hello", 0, "trusting");
38
+ expect(content.toJSON()).toEqual(["hello"]);
39
+ content.append("world", 0, "trusting");
40
+ expect(content.toJSON()).toEqual(["hello", "world"]);
41
+ content.prepend("beautiful", 1, "trusting");
42
+ expect(content.toJSON()).toEqual(["hello", "beautiful", "world"]);
43
+ content.prepend("hooray", 3, "trusting");
44
+ expect(content.toJSON()).toEqual(["hello", "beautiful", "world", "hooray"]);
45
+ content.replace(2, "universe", "trusting");
46
+ expect(content.toJSON()).toEqual([
47
+ "hello",
48
+ "beautiful",
49
+ "universe",
50
+ "hooray",
51
+ ]);
52
+ content.delete(2, "trusting");
53
+ expect(content.toJSON()).toEqual(["hello", "beautiful", "hooray"]);
54
+ });
55
+
56
+ test("Push is equivalent to append after last item", () => {
57
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
58
+
59
+ const coValue = node.createCoValue({
60
+ type: "colist",
61
+ ruleset: { type: "unsafeAllowAll" },
62
+ meta: null,
63
+ ...Crypto.createdNowUnique(),
64
+ });
65
+
66
+ const content = expectList(coValue.getCurrentContent());
67
+
68
+ expect(content.type).toEqual("colist");
69
+
70
+ content.append("hello", 0, "trusting");
71
+ expect(content.toJSON()).toEqual(["hello"]);
72
+ content.append("world", undefined, "trusting");
73
+ expect(content.toJSON()).toEqual(["hello", "world"]);
74
+ content.append("hooray", undefined, "trusting");
75
+ expect(content.toJSON()).toEqual(["hello", "world", "hooray"]);
76
+ });
77
+
78
+ test("Can push into empty list", () => {
79
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
80
+
81
+ const coValue = node.createCoValue({
82
+ type: "colist",
83
+ ruleset: { type: "unsafeAllowAll" },
84
+ meta: null,
85
+ ...Crypto.createdNowUnique(),
86
+ });
87
+
88
+ const content = expectList(coValue.getCurrentContent());
89
+
90
+ expect(content.type).toEqual("colist");
91
+
92
+ content.append("hello", undefined, "trusting");
93
+ expect(content.toJSON()).toEqual(["hello"]);
94
+ });
@@ -0,0 +1,162 @@
1
+ import { expect, test } from "vitest";
2
+ import { expectMap } from "../coValue.js";
3
+ import { WasmCrypto } from "../index.js";
4
+ import { LocalNode } from "../localNode.js";
5
+ import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
6
+ import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
7
+
8
+ const Crypto = await WasmCrypto.create();
9
+
10
+ test("Empty CoMap works", () => {
11
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
12
+
13
+ const coValue = node.createCoValue({
14
+ type: "comap",
15
+ ruleset: { type: "unsafeAllowAll" },
16
+ meta: null,
17
+ ...Crypto.createdNowUnique(),
18
+ });
19
+
20
+ const content = expectMap(coValue.getCurrentContent());
21
+
22
+ expect(content.type).toEqual("comap");
23
+ expect([...content.keys()]).toEqual([]);
24
+ expect(content.toJSON()).toEqual({});
25
+ });
26
+
27
+ test("Can insert and delete CoMap entries in edit()", () => {
28
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
29
+
30
+ const coValue = node.createCoValue({
31
+ type: "comap",
32
+ ruleset: { type: "unsafeAllowAll" },
33
+ meta: null,
34
+ ...Crypto.createdNowUnique(),
35
+ });
36
+
37
+ const content = expectMap(coValue.getCurrentContent());
38
+
39
+ expect(content.type).toEqual("comap");
40
+
41
+ content.set("hello", "world", "trusting");
42
+ expect(content.get("hello")).toEqual("world");
43
+ content.set("foo", "bar", "trusting");
44
+ expect(content.get("foo")).toEqual("bar");
45
+ expect([...content.keys()]).toEqual(["hello", "foo"]);
46
+ content.delete("foo", "trusting");
47
+ expect(content.get("foo")).toEqual(undefined);
48
+ expect(content.keys()).toEqual(["hello"]);
49
+ });
50
+
51
+ test("Can get CoMap entry values at different points in time", () => {
52
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
53
+
54
+ const coValue = node.createCoValue({
55
+ type: "comap",
56
+ ruleset: { type: "unsafeAllowAll" },
57
+ meta: null,
58
+ ...Crypto.createdNowUnique(),
59
+ });
60
+
61
+ const content = expectMap(coValue.getCurrentContent());
62
+
63
+ expect(content.type).toEqual("comap");
64
+
65
+ const beforeA = Date.now();
66
+ while (Date.now() < beforeA + 10) {
67
+ /* hot sleep */
68
+ }
69
+ content.set("hello", "A", "trusting");
70
+ const beforeB = Date.now();
71
+ while (Date.now() < beforeB + 10) {
72
+ /* hot sleep */
73
+ }
74
+ content.set("hello", "B", "trusting");
75
+ const beforeC = Date.now();
76
+ while (Date.now() < beforeC + 10) {
77
+ /* hot sleep */
78
+ }
79
+ content.set("hello", "C", "trusting");
80
+ expect(content.get("hello")).toEqual("C");
81
+ expect(content.atTime(Date.now()).get("hello")).toEqual("C");
82
+ expect(content.atTime(beforeA).get("hello")).toEqual(undefined);
83
+ expect(content.atTime(beforeB).get("hello")).toEqual("A");
84
+ expect(content.atTime(beforeC).get("hello")).toEqual("B");
85
+ });
86
+
87
+ test("Can get all historic values of key in CoMap", () => {
88
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
89
+
90
+ const coValue = node.createCoValue({
91
+ type: "comap",
92
+ ruleset: { type: "unsafeAllowAll" },
93
+ meta: null,
94
+ ...Crypto.createdNowUnique(),
95
+ });
96
+
97
+ const content = expectMap(coValue.getCurrentContent());
98
+
99
+ expect(content.type).toEqual("comap");
100
+
101
+ content.set("hello", "A", "trusting");
102
+ const editA = content.lastEditAt("hello");
103
+ content.set("hello", "B", "trusting");
104
+ const editB = content.lastEditAt("hello");
105
+ content.delete("hello", "trusting");
106
+ const editDel = content.lastEditAt("hello");
107
+ content.set("hello", "C", "trusting");
108
+ const editC = content.lastEditAt("hello");
109
+ expect([...content.editsAt("hello")]).toEqual([
110
+ {
111
+ tx: editA!.tx,
112
+ by: node.account.id,
113
+ value: "A",
114
+ at: editA?.at,
115
+ },
116
+ {
117
+ tx: editB!.tx,
118
+ by: node.account.id,
119
+ value: "B",
120
+ at: editB?.at,
121
+ },
122
+ {
123
+ tx: editDel!.tx,
124
+ by: node.account.id,
125
+ value: undefined,
126
+ at: editDel?.at,
127
+ },
128
+ {
129
+ tx: editC!.tx,
130
+ by: node.account.id,
131
+ value: "C",
132
+ at: editC?.at,
133
+ },
134
+ ]);
135
+ });
136
+
137
+ test("Can get last tx ID for a key in CoMap", () => {
138
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
139
+
140
+ const coValue = node.createCoValue({
141
+ type: "comap",
142
+ ruleset: { type: "unsafeAllowAll" },
143
+ meta: null,
144
+ ...Crypto.createdNowUnique(),
145
+ });
146
+
147
+ const content = expectMap(coValue.getCurrentContent());
148
+
149
+ expect(content.type).toEqual("comap");
150
+
151
+ expect(content.lastEditAt("hello")).toEqual(undefined);
152
+ content.set("hello", "A", "trusting");
153
+ const sessionID = content.lastEditAt("hello")?.tx.sessionID;
154
+ expect(sessionID && accountOrAgentIDfromSessionID(sessionID)).toEqual(
155
+ node.account.id,
156
+ );
157
+ expect(content.lastEditAt("hello")?.tx.txIndex).toEqual(0);
158
+ content.set("hello", "B", "trusting");
159
+ expect(content.lastEditAt("hello")?.tx.txIndex).toEqual(1);
160
+ content.set("hello", "C", "trusting");
161
+ expect(content.lastEditAt("hello")?.tx.txIndex).toEqual(2);
162
+ });