cojson 0.13.17 → 0.13.20

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 (180) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dist/PeerState.d.ts +4 -1
  4. package/dist/PeerState.d.ts.map +1 -1
  5. package/dist/PeerState.js +16 -36
  6. package/dist/PeerState.js.map +1 -1
  7. package/dist/SyncStateManager.d.ts.map +1 -1
  8. package/dist/SyncStateManager.js +2 -3
  9. package/dist/SyncStateManager.js.map +1 -1
  10. package/dist/coValue.d.ts +4 -4
  11. package/dist/coValue.d.ts.map +1 -1
  12. package/dist/coValue.js +4 -4
  13. package/dist/coValue.js.map +1 -1
  14. package/dist/coValueCore/coValueCore.d.ts +143 -0
  15. package/dist/coValueCore/coValueCore.d.ts.map +1 -0
  16. package/dist/{coValueCore.js → coValueCore/coValueCore.js} +325 -253
  17. package/dist/coValueCore/coValueCore.js.map +1 -0
  18. package/dist/coValueCore/verifiedState.d.ts +65 -0
  19. package/dist/coValueCore/verifiedState.d.ts.map +1 -0
  20. package/dist/coValueCore/verifiedState.js +210 -0
  21. package/dist/coValueCore/verifiedState.js.map +1 -0
  22. package/dist/coValues/account.d.ts +8 -10
  23. package/dist/coValues/account.d.ts.map +1 -1
  24. package/dist/coValues/account.js +12 -13
  25. package/dist/coValues/account.js.map +1 -1
  26. package/dist/coValues/coList.d.ts +3 -3
  27. package/dist/coValues/coList.d.ts.map +1 -1
  28. package/dist/coValues/coList.js +6 -3
  29. package/dist/coValues/coList.js.map +1 -1
  30. package/dist/coValues/coMap.d.ts +3 -3
  31. package/dist/coValues/coMap.d.ts.map +1 -1
  32. package/dist/coValues/coMap.js +3 -3
  33. package/dist/coValues/coMap.js.map +1 -1
  34. package/dist/coValues/coPlainText.d.ts +2 -2
  35. package/dist/coValues/coPlainText.d.ts.map +1 -1
  36. package/dist/coValues/coPlainText.js +4 -4
  37. package/dist/coValues/coPlainText.js.map +1 -1
  38. package/dist/coValues/coStream.d.ts +3 -3
  39. package/dist/coValues/coStream.d.ts.map +1 -1
  40. package/dist/coValues/coStream.js +3 -3
  41. package/dist/coValues/coStream.js.map +1 -1
  42. package/dist/coValues/group.d.ts +7 -2
  43. package/dist/coValues/group.d.ts.map +1 -1
  44. package/dist/coValues/group.js +29 -26
  45. package/dist/coValues/group.js.map +1 -1
  46. package/dist/coreToCoValue.d.ts +2 -2
  47. package/dist/coreToCoValue.d.ts.map +1 -1
  48. package/dist/coreToCoValue.js +10 -14
  49. package/dist/coreToCoValue.js.map +1 -1
  50. package/dist/exports.d.ts +6 -5
  51. package/dist/exports.d.ts.map +1 -1
  52. package/dist/exports.js +3 -4
  53. package/dist/exports.js.map +1 -1
  54. package/dist/localNode.d.ts +30 -24
  55. package/dist/localNode.d.ts.map +1 -1
  56. package/dist/localNode.js +147 -173
  57. package/dist/localNode.js.map +1 -1
  58. package/dist/permissions.d.ts +2 -1
  59. package/dist/permissions.d.ts.map +1 -1
  60. package/dist/permissions.js +15 -11
  61. package/dist/permissions.js.map +1 -1
  62. package/dist/priority.d.ts +1 -1
  63. package/dist/priority.d.ts.map +1 -1
  64. package/dist/streamUtils.d.ts +5 -5
  65. package/dist/streamUtils.d.ts.map +1 -1
  66. package/dist/streamUtils.js +5 -20
  67. package/dist/streamUtils.js.map +1 -1
  68. package/dist/sync.d.ts +8 -6
  69. package/dist/sync.d.ts.map +1 -1
  70. package/dist/sync.js +121 -74
  71. package/dist/sync.js.map +1 -1
  72. package/dist/tests/PeerState.test.js +0 -31
  73. package/dist/tests/PeerState.test.js.map +1 -1
  74. package/dist/tests/SyncStateManager.test.js +41 -6
  75. package/dist/tests/SyncStateManager.test.js.map +1 -1
  76. package/dist/tests/account.test.js +16 -0
  77. package/dist/tests/account.test.js.map +1 -1
  78. package/dist/tests/coList.test.js +19 -16
  79. package/dist/tests/coList.test.js.map +1 -1
  80. package/dist/tests/coMap.test.js +12 -13
  81. package/dist/tests/coMap.test.js.map +1 -1
  82. package/dist/tests/coPlainText.test.js +9 -10
  83. package/dist/tests/coPlainText.test.js.map +1 -1
  84. package/dist/tests/coStream.test.js +22 -17
  85. package/dist/tests/coStream.test.js.map +1 -1
  86. package/dist/tests/coValueCore.test.js +22 -28
  87. package/dist/tests/coValueCore.test.js.map +1 -1
  88. package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
  89. package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
  90. package/dist/tests/{coValueState.test.js → coValueCoreLoadingState.test.js} +62 -46
  91. package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
  92. package/dist/tests/group.test.js +42 -43
  93. package/dist/tests/group.test.js.map +1 -1
  94. package/dist/tests/messagesTestUtils.d.ts +2 -2
  95. package/dist/tests/messagesTestUtils.d.ts.map +1 -1
  96. package/dist/tests/messagesTestUtils.js +1 -1
  97. package/dist/tests/messagesTestUtils.js.map +1 -1
  98. package/dist/tests/permissions.test.js +224 -292
  99. package/dist/tests/permissions.test.js.map +1 -1
  100. package/dist/tests/priority.test.js +13 -14
  101. package/dist/tests/priority.test.js.map +1 -1
  102. package/dist/tests/sync.auth.test.d.ts +2 -0
  103. package/dist/tests/sync.auth.test.d.ts.map +1 -0
  104. package/dist/tests/sync.auth.test.js +190 -0
  105. package/dist/tests/sync.auth.test.js.map +1 -0
  106. package/dist/tests/sync.load.test.js +6 -6
  107. package/dist/tests/sync.load.test.js.map +1 -1
  108. package/dist/tests/sync.mesh.test.js +25 -12
  109. package/dist/tests/sync.mesh.test.js.map +1 -1
  110. package/dist/tests/sync.peerReconciliation.test.js +19 -19
  111. package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
  112. package/dist/tests/sync.storage.test.js +20 -13
  113. package/dist/tests/sync.storage.test.js.map +1 -1
  114. package/dist/tests/sync.test.js +32 -39
  115. package/dist/tests/sync.test.js.map +1 -1
  116. package/dist/tests/sync.upload.test.js +126 -37
  117. package/dist/tests/sync.upload.test.js.map +1 -1
  118. package/dist/tests/testUtils.d.ts +35 -17
  119. package/dist/tests/testUtils.d.ts.map +1 -1
  120. package/dist/tests/testUtils.js +103 -79
  121. package/dist/tests/testUtils.js.map +1 -1
  122. package/dist/typeUtils/expectGroup.js +1 -1
  123. package/dist/typeUtils/expectGroup.js.map +1 -1
  124. package/package.json +1 -1
  125. package/src/PeerState.ts +19 -40
  126. package/src/SyncStateManager.ts +2 -3
  127. package/src/coValue.ts +11 -8
  128. package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +478 -422
  129. package/src/coValueCore/verifiedState.ts +376 -0
  130. package/src/coValues/account.ts +20 -25
  131. package/src/coValues/coList.ts +12 -6
  132. package/src/coValues/coMap.ts +9 -6
  133. package/src/coValues/coPlainText.ts +9 -6
  134. package/src/coValues/coStream.ts +9 -6
  135. package/src/coValues/group.ts +50 -28
  136. package/src/coreToCoValue.ts +14 -15
  137. package/src/exports.ts +9 -7
  138. package/src/localNode.ts +236 -275
  139. package/src/permissions.ts +18 -12
  140. package/src/priority.ts +1 -1
  141. package/src/streamUtils.ts +7 -34
  142. package/src/sync.ts +146 -84
  143. package/src/tests/PeerState.test.ts +0 -37
  144. package/src/tests/SyncStateManager.test.ts +56 -6
  145. package/src/tests/account.test.ts +24 -0
  146. package/src/tests/coList.test.ts +21 -15
  147. package/src/tests/coMap.test.ts +12 -13
  148. package/src/tests/coPlainText.test.ts +12 -9
  149. package/src/tests/coStream.test.ts +25 -16
  150. package/src/tests/coValueCore.test.ts +30 -27
  151. package/src/tests/{coValueState.test.ts → coValueCoreLoadingState.test.ts} +67 -57
  152. package/src/tests/group.test.ts +44 -69
  153. package/src/tests/messagesTestUtils.ts +3 -8
  154. package/src/tests/permissions.test.ts +283 -449
  155. package/src/tests/priority.test.ts +17 -13
  156. package/src/tests/sync.auth.test.ts +246 -0
  157. package/src/tests/sync.load.test.ts +7 -6
  158. package/src/tests/sync.mesh.test.ts +25 -12
  159. package/src/tests/sync.peerReconciliation.test.ts +25 -25
  160. package/src/tests/sync.storage.test.ts +20 -13
  161. package/src/tests/sync.test.ts +43 -43
  162. package/src/tests/sync.upload.test.ts +157 -37
  163. package/src/tests/testUtils.ts +143 -96
  164. package/src/typeUtils/expectGroup.ts +1 -1
  165. package/dist/CoValuesStore.d.ts +0 -14
  166. package/dist/CoValuesStore.d.ts.map +0 -1
  167. package/dist/CoValuesStore.js +0 -32
  168. package/dist/CoValuesStore.js.map +0 -1
  169. package/dist/coValueCore.d.ts +0 -142
  170. package/dist/coValueCore.d.ts.map +0 -1
  171. package/dist/coValueCore.js.map +0 -1
  172. package/dist/coValueState.d.ts +0 -34
  173. package/dist/coValueState.d.ts.map +0 -1
  174. package/dist/coValueState.js +0 -190
  175. package/dist/coValueState.js.map +0 -1
  176. package/dist/tests/coValueState.test.d.ts +0 -2
  177. package/dist/tests/coValueState.test.d.ts.map +0 -1
  178. package/dist/tests/coValueState.test.js.map +0 -1
  179. package/src/CoValuesStore.ts +0 -41
  180. package/src/coValueState.ts +0 -245
@@ -1,24 +1,23 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { CoValueState } from "../coValueState.js";
3
2
  import { RawCoList } from "../coValues/coList.js";
4
3
  import { RawCoMap } from "../coValues/coMap.js";
5
4
  import { RawCoStream } from "../coValues/coStream.js";
6
5
  import { RawBinaryCoStream } from "../coValues/coStream.js";
7
6
  import { WasmCrypto } from "../crypto/WasmCrypto.js";
8
7
  import { RawAccountID } from "../exports.js";
9
- import { LocalNode } from "../localNode.js";
10
8
  import {
11
9
  createThreeConnectedNodes,
12
10
  createTwoConnectedNodes,
13
11
  loadCoValueOrFail,
14
- randomAnonymousAccountAndSessionID,
12
+ nodeWithRandomAgentAndSessionID,
13
+ randomAgentAndSessionID,
15
14
  waitFor,
16
15
  } from "./testUtils.js";
17
16
 
18
17
  const Crypto = await WasmCrypto.create();
19
18
 
20
19
  test("Can create a RawCoMap in a group", () => {
21
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
20
+ const node = nodeWithRandomAgentAndSessionID();
22
21
 
23
22
  const group = node.createGroup();
24
23
 
@@ -29,7 +28,7 @@ test("Can create a RawCoMap in a group", () => {
29
28
  });
30
29
 
31
30
  test("Can create a CoList in a group", () => {
32
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
31
+ const node = nodeWithRandomAgentAndSessionID();
33
32
 
34
33
  const group = node.createGroup();
35
34
 
@@ -40,7 +39,7 @@ test("Can create a CoList in a group", () => {
40
39
  });
41
40
 
42
41
  test("Can create a CoStream in a group", () => {
43
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
42
+ const node = nodeWithRandomAgentAndSessionID();
44
43
 
45
44
  const group = node.createGroup();
46
45
 
@@ -51,7 +50,7 @@ test("Can create a CoStream in a group", () => {
51
50
  });
52
51
 
53
52
  test("Can create a FileStream in a group", () => {
54
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
53
+ const node = nodeWithRandomAgentAndSessionID();
55
54
 
56
55
  const group = node.createGroup();
57
56
 
@@ -100,7 +99,7 @@ test("Remove a member from a group where the admin role is inherited", async ()
100
99
 
101
100
  // The node1 account removes the reader from the group
102
101
  // The reader should be automatically kicked out of the child group
103
- await group.removeMember(node3.node.account);
102
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
104
103
 
105
104
  await group.core.waitForSync();
106
105
 
@@ -150,7 +149,7 @@ test("An admin should be able to rotate the readKey on child groups and keep acc
150
149
  // The node1 account removes the reader from the group
151
150
  // In this case we want to ensure that node1 is still able to read new coValues
152
151
  // Even if some childs are not available when the readKey is rotated
153
- await group.removeMember(node3.node.account);
152
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
154
153
  await group.core.waitForSync();
155
154
 
156
155
  const map = childGroup.createMap();
@@ -191,7 +190,7 @@ test("An admin should be able to rotate the readKey on child groups even if it w
191
190
  // The node1 account removes the reader from the group
192
191
  // In this case we want to ensure that node1 is still able to read new coValues
193
192
  // Even if some childs are not available when the readKey is rotated
194
- await group.removeMember(node3.node.account);
193
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
195
194
  await group.core.waitForSync();
196
195
 
197
196
  const map = childGroup.createMap();
@@ -234,7 +233,7 @@ test("An admin should be able to rotate the readKey on child groups even if it w
234
233
  // The node1 account removes the reader from the group
235
234
  // In this case we want to ensure that node1 is still able to read new coValues
236
235
  // Even if some childs are not available when the readKey is rotated
237
- await group.removeMember(node3.node.account);
236
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
238
237
  await group.core.waitForSync();
239
238
 
240
239
  const map = childGroup.createMap();
@@ -268,7 +267,7 @@ test("A user add after a key rotation should have access to the old transactions
268
267
 
269
268
  await map.core.waitForSync();
270
269
 
271
- await group.removeMember(node3.node.account);
270
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
272
271
  group.addMember(
273
272
  await loadCoValueOrFail(node1.node, node3.accountID),
274
273
  "reader",
@@ -295,7 +294,7 @@ test("Invites should have access to the new keys", async () => {
295
294
 
296
295
  const invite = group.createInvite("admin");
297
296
 
298
- await group.removeMember(node3.node.account);
297
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
299
298
 
300
299
  const map = group.createMap();
301
300
  map.set("test", "Written from node1");
@@ -454,7 +453,7 @@ describe("writeOnly", () => {
454
453
 
455
454
  await map.core.waitForSync();
456
455
 
457
- await group.removeMember(node3.node.account);
456
+ await group.removeMember(node3.node.expectCurrentAccount("node3"));
458
457
 
459
458
  await group.core.waitForSync();
460
459
 
@@ -491,10 +490,8 @@ describe("writeOnly", () => {
491
490
  "writer",
492
491
  );
493
492
 
494
- node2.node.coValuesStore.coValues.delete(map.id);
495
- expect(node2.node.coValuesStore.get(map.id)?.highLevelState).toBe(
496
- "unknown",
497
- );
493
+ node2.node.internalDeleteCoValue(map.id);
494
+ expect(node2.node.getCoValue(map.id)?.loadingState).toBe("unknown");
498
495
 
499
496
  const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
500
497
 
@@ -753,7 +750,6 @@ describe("extend with role mapping", () => {
753
750
  const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
754
751
 
755
752
  expect(mapOnNode2.get("test")).toEqual("Written from the admin");
756
-
757
753
  mapOnNode2.set("test", "Written from the inherited role");
758
754
  expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");
759
755
 
@@ -918,96 +914,75 @@ describe("extend with role mapping", () => {
918
914
 
919
915
  describe("roleOf", () => {
920
916
  test("returns direct role assignments", () => {
921
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
917
+ const node = nodeWithRandomAgentAndSessionID();
922
918
  const group = node.createGroup();
923
- const account = new LocalNode(
924
- ...randomAnonymousAccountAndSessionID(),
925
- Crypto,
926
- ).account;
919
+ const [agent2] = randomAgentAndSessionID();
927
920
 
928
- group.addMember(account, "writer");
929
- expect(group.roleOf(account.id as RawAccountID)).toEqual("writer");
921
+ group.addMember(agent2, "writer");
922
+ expect(group.roleOfInternal(agent2.id)).toEqual("writer");
930
923
  });
931
924
 
932
925
  test("returns undefined for non-members", () => {
933
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
926
+ const node = nodeWithRandomAgentAndSessionID();
934
927
  const group = node.createGroup();
935
- const account = new LocalNode(
936
- ...randomAnonymousAccountAndSessionID(),
937
- Crypto,
938
- ).account;
928
+ const [agent2] = randomAgentAndSessionID();
939
929
 
940
- expect(group.roleOf(account.id as RawAccountID)).toEqual(undefined);
930
+ expect(group.roleOfInternal(agent2.id)).toEqual(undefined);
941
931
  });
942
932
 
943
933
  test("revoked roles return undefined", () => {
944
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
934
+ const node = nodeWithRandomAgentAndSessionID();
945
935
  const group = node.createGroup();
946
- const account = new LocalNode(
947
- ...randomAnonymousAccountAndSessionID(),
948
- Crypto,
949
- ).account;
950
-
951
- group.addMember(account, "writer");
952
- group.removeMemberInternal(account);
953
- expect(group.roleOf(account.id as RawAccountID)).toEqual(undefined);
936
+ const [agent2] = randomAgentAndSessionID();
937
+
938
+ group.addMember(agent2, "writer");
939
+ group.removeMemberInternal(agent2);
940
+ expect(group.roleOfInternal(agent2.id)).toEqual(undefined);
954
941
  });
955
942
 
956
943
  test("everyone role applies to all accounts", () => {
957
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
944
+ const node = nodeWithRandomAgentAndSessionID();
958
945
  const group = node.createGroup();
959
- const account = new LocalNode(
960
- ...randomAnonymousAccountAndSessionID(),
961
- Crypto,
962
- ).account;
946
+ const [agent2, sessionID2] = randomAgentAndSessionID();
963
947
 
964
948
  group.addMemberInternal("everyone", "reader");
965
- expect(group.roleOf(account.id as RawAccountID)).toEqual("reader");
949
+ expect(group.roleOfInternal(agent2.id)).toEqual("reader");
966
950
  });
967
951
 
968
952
  test("account role overrides everyone role", () => {
969
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
953
+ const node = nodeWithRandomAgentAndSessionID();
970
954
  const group = node.createGroup();
971
- const account = new LocalNode(
972
- ...randomAnonymousAccountAndSessionID(),
973
- Crypto,
974
- ).account;
955
+ const [agent2, sessionID2] = randomAgentAndSessionID();
975
956
 
976
957
  group.addMemberInternal("everyone", "writer");
977
- group.addMember(account, "reader");
978
- expect(group.roleOf(account.id as RawAccountID)).toEqual("reader");
958
+ group.addMember(agent2, "reader");
959
+ expect(group.roleOfInternal(agent2.id)).toEqual("reader");
979
960
  });
980
961
 
981
962
  test("Revoking access on everyone role should not affect existing members", () => {
982
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
963
+ const node = nodeWithRandomAgentAndSessionID();
983
964
  const group = node.createGroup();
984
- const account = new LocalNode(
985
- ...randomAnonymousAccountAndSessionID(),
986
- Crypto,
987
- ).account;
965
+ const [agent2, sessionID2] = randomAgentAndSessionID();
988
966
 
989
967
  group.addMemberInternal("everyone", "reader");
990
- group.addMember(account, "writer");
968
+ group.addMember(agent2, "writer");
991
969
  group.removeMemberInternal("everyone");
992
- expect(group.roleOf(account.id as RawAccountID)).toEqual("writer");
993
- expect(group.roleOf("123" as RawAccountID)).toEqual(undefined);
970
+ expect(group.roleOfInternal(agent2.id)).toEqual("writer");
971
+ expect(group.roleOfInternal("123" as RawAccountID)).toEqual(undefined);
994
972
  });
995
973
 
996
974
  test("Everyone role is inherited following the most permissive algorithm", () => {
997
- const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
975
+ const node = nodeWithRandomAgentAndSessionID();
998
976
  const group = node.createGroup();
999
- const account = new LocalNode(
1000
- ...randomAnonymousAccountAndSessionID(),
1001
- Crypto,
1002
- ).account;
977
+ const [agent2, sessionID2] = randomAgentAndSessionID();
1003
978
 
1004
979
  const parentGroup = node.createGroup();
1005
980
  parentGroup.addMemberInternal("everyone", "writer");
1006
981
 
1007
982
  group.extend(parentGroup);
1008
- group.addMember(account, "reader");
983
+ group.addMember(agent2, "reader");
1009
984
 
1010
- expect(group.roleOf(account.id as RawAccountID)).toEqual("writer");
985
+ expect(group.roleOfInternal(agent2.id)).toEqual("writer");
1011
986
  });
1012
987
  test("roleOf should prioritize explicit account role over everyone role in same group", async () => {
1013
988
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
@@ -1,10 +1,5 @@
1
- import { CoValueCore, LocalNode, RawControlledAccount } from "../exports";
2
- import {
3
- CoValueKnownState,
4
- NewContentMessage,
5
- Peer,
6
- SyncMessage,
7
- } from "../sync";
1
+ import { CoValueCore, LocalNode } from "../exports";
2
+ import { CoValueKnownState, NewContentMessage, SyncMessage } from "../sync";
8
3
 
9
4
  function simplifySessions(msg: CoValueKnownState) {
10
5
  const count = Object.values(msg.sessions).reduce(
@@ -64,7 +59,7 @@ export function toSimplifiedMessages(
64
59
  }
65
60
 
66
61
  export function nodeRelatedKnownCoValues(node: LocalNode, name: string) {
67
- const account = node.account as RawControlledAccount;
62
+ const account = node.expectCurrentAccount("relatedKnownCoValues");
68
63
  const profileID = account.get("profile");
69
64
  const profile = profileID && node.expectCoValueLoaded(profileID);
70
65
  return {