cojson 0.10.8 → 0.11.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 (180) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/dist/CoValuesStore.d.ts +12 -0
  4. package/dist/CoValuesStore.d.ts.map +1 -0
  5. package/dist/PeerKnownStates.d.ts +38 -0
  6. package/dist/PeerKnownStates.d.ts.map +1 -0
  7. package/dist/PeerState.d.ts +46 -0
  8. package/dist/PeerState.d.ts.map +1 -0
  9. package/dist/PriorityBasedMessageQueue.d.ts +18 -0
  10. package/dist/PriorityBasedMessageQueue.d.ts.map +1 -0
  11. package/dist/SyncStateManager.d.ts +20 -0
  12. package/dist/SyncStateManager.d.ts.map +1 -0
  13. package/dist/base64url.d.ts +3 -0
  14. package/dist/base64url.d.ts.map +1 -0
  15. package/dist/base64url.test.d.ts +2 -0
  16. package/dist/base64url.test.d.ts.map +1 -0
  17. package/dist/coValue.d.ts +52 -0
  18. package/dist/coValue.d.ts.map +1 -0
  19. package/dist/coValueCore.d.ts +143 -0
  20. package/dist/coValueCore.d.ts.map +1 -0
  21. package/dist/coValueCore.js +3 -9
  22. package/dist/coValueCore.js.map +1 -1
  23. package/dist/coValueState.d.ts +58 -0
  24. package/dist/coValueState.d.ts.map +1 -0
  25. package/dist/coValues/account.d.ts +69 -0
  26. package/dist/coValues/account.d.ts.map +1 -0
  27. package/dist/coValues/account.js +9 -10
  28. package/dist/coValues/account.js.map +1 -1
  29. package/dist/coValues/coList.d.ts +163 -0
  30. package/dist/coValues/coList.d.ts.map +1 -0
  31. package/dist/coValues/coMap.d.ts +142 -0
  32. package/dist/coValues/coMap.d.ts.map +1 -0
  33. package/dist/coValues/coPlainText.d.ts +33 -0
  34. package/dist/coValues/coPlainText.d.ts.map +1 -0
  35. package/dist/coValues/coStream.d.ts +109 -0
  36. package/dist/coValues/coStream.d.ts.map +1 -0
  37. package/dist/coValues/group.d.ts +143 -0
  38. package/dist/coValues/group.d.ts.map +1 -0
  39. package/dist/coValues/group.js +52 -6
  40. package/dist/coValues/group.js.map +1 -1
  41. package/dist/coreToCoValue.d.ts +15 -0
  42. package/dist/coreToCoValue.d.ts.map +1 -0
  43. package/dist/crypto/PureJSCrypto.d.ts +50 -0
  44. package/dist/crypto/PureJSCrypto.d.ts.map +1 -0
  45. package/dist/crypto/WasmCrypto.d.ts +49 -0
  46. package/dist/crypto/WasmCrypto.d.ts.map +1 -0
  47. package/dist/crypto/crypto.d.ts +142 -0
  48. package/dist/crypto/crypto.d.ts.map +1 -0
  49. package/dist/exports.d.ts +84 -0
  50. package/dist/exports.d.ts.map +1 -0
  51. package/dist/ids.d.ts +23 -0
  52. package/dist/ids.d.ts.map +1 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/jsonStringify.d.ts +7 -0
  56. package/dist/jsonStringify.d.ts.map +1 -0
  57. package/dist/jsonValue.d.ts +45 -0
  58. package/dist/jsonValue.d.ts.map +1 -0
  59. package/dist/localNode.d.ts +111 -0
  60. package/dist/localNode.d.ts.map +1 -0
  61. package/dist/localNode.js +3 -5
  62. package/dist/localNode.js.map +1 -1
  63. package/dist/logger.d.ts +33 -0
  64. package/dist/logger.d.ts.map +1 -0
  65. package/dist/media.d.ts +8 -0
  66. package/dist/media.d.ts.map +1 -0
  67. package/dist/permissions.d.ts +24 -0
  68. package/dist/permissions.d.ts.map +1 -0
  69. package/dist/permissions.js +5 -9
  70. package/dist/permissions.js.map +1 -1
  71. package/dist/priority.d.ts +19 -0
  72. package/dist/priority.d.ts.map +1 -0
  73. package/dist/storage/FileSystem.d.ts +37 -0
  74. package/dist/storage/FileSystem.d.ts.map +1 -0
  75. package/dist/storage/chunksAndKnownStates.d.ts +7 -0
  76. package/dist/storage/chunksAndKnownStates.d.ts.map +1 -0
  77. package/dist/storage/index.d.ts +52 -0
  78. package/dist/storage/index.d.ts.map +1 -0
  79. package/dist/streamUtils.d.ts +13 -0
  80. package/dist/streamUtils.d.ts.map +1 -0
  81. package/dist/sync.d.ts +97 -0
  82. package/dist/sync.d.ts.map +1 -0
  83. package/dist/tests/PeerKnownStates.test.d.ts +2 -0
  84. package/dist/tests/PeerKnownStates.test.d.ts.map +1 -0
  85. package/dist/tests/PeerKnownStates.test.js +82 -0
  86. package/dist/tests/PeerKnownStates.test.js.map +1 -0
  87. package/dist/tests/PeerState.test.d.ts +2 -0
  88. package/dist/tests/PeerState.test.d.ts.map +1 -0
  89. package/dist/tests/PeerState.test.js +188 -0
  90. package/dist/tests/PeerState.test.js.map +1 -0
  91. package/dist/tests/PriorityBasedMessageQueue.test.d.ts +2 -0
  92. package/dist/tests/PriorityBasedMessageQueue.test.d.ts.map +1 -0
  93. package/dist/tests/PriorityBasedMessageQueue.test.js +120 -0
  94. package/dist/tests/PriorityBasedMessageQueue.test.js.map +1 -0
  95. package/dist/tests/SyncStateManager.test.d.ts +2 -0
  96. package/dist/tests/SyncStateManager.test.d.ts.map +1 -0
  97. package/dist/tests/SyncStateManager.test.js +127 -0
  98. package/dist/tests/SyncStateManager.test.js.map +1 -0
  99. package/dist/tests/account.test.d.ts +2 -0
  100. package/dist/tests/account.test.d.ts.map +1 -0
  101. package/dist/tests/account.test.js +66 -0
  102. package/dist/tests/account.test.js.map +1 -0
  103. package/dist/tests/coList.test.d.ts +2 -0
  104. package/dist/tests/coList.test.d.ts.map +1 -0
  105. package/dist/tests/coList.test.js +120 -0
  106. package/dist/tests/coList.test.js.map +1 -0
  107. package/dist/tests/coMap.test.d.ts +2 -0
  108. package/dist/tests/coMap.test.d.ts.map +1 -0
  109. package/dist/tests/coMap.test.js +164 -0
  110. package/dist/tests/coMap.test.js.map +1 -0
  111. package/dist/tests/coPlainText.test.d.ts +2 -0
  112. package/dist/tests/coPlainText.test.d.ts.map +1 -0
  113. package/dist/tests/coPlainText.test.js +99 -0
  114. package/dist/tests/coPlainText.test.js.map +1 -0
  115. package/dist/tests/coStream.test.d.ts +2 -0
  116. package/dist/tests/coStream.test.d.ts.map +1 -0
  117. package/dist/tests/coStream.test.js +206 -0
  118. package/dist/tests/coStream.test.js.map +1 -0
  119. package/dist/tests/coValueCore.test.d.ts +2 -0
  120. package/dist/tests/coValueCore.test.d.ts.map +1 -0
  121. package/dist/tests/coValueCore.test.js +164 -0
  122. package/dist/tests/coValueCore.test.js.map +1 -0
  123. package/dist/tests/coValueState.test.d.ts +2 -0
  124. package/dist/tests/coValueState.test.d.ts.map +1 -0
  125. package/dist/tests/coValueState.test.js +364 -0
  126. package/dist/tests/coValueState.test.js.map +1 -0
  127. package/dist/tests/crypto.test.d.ts +2 -0
  128. package/dist/tests/crypto.test.d.ts.map +1 -0
  129. package/dist/tests/crypto.test.js +144 -0
  130. package/dist/tests/crypto.test.js.map +1 -0
  131. package/dist/tests/cryptoImpl.test.d.ts +2 -0
  132. package/dist/tests/cryptoImpl.test.d.ts.map +1 -0
  133. package/dist/tests/cryptoImpl.test.js +144 -0
  134. package/dist/tests/cryptoImpl.test.js.map +1 -0
  135. package/dist/tests/group.test.d.ts +2 -0
  136. package/dist/tests/group.test.d.ts.map +1 -0
  137. package/dist/tests/group.test.js +576 -0
  138. package/dist/tests/group.test.js.map +1 -0
  139. package/dist/tests/logger.test.d.ts +2 -0
  140. package/dist/tests/logger.test.d.ts.map +1 -0
  141. package/dist/tests/logger.test.js +118 -0
  142. package/dist/tests/logger.test.js.map +1 -0
  143. package/dist/tests/permissions.test.d.ts +2 -0
  144. package/dist/tests/permissions.test.d.ts.map +1 -0
  145. package/dist/tests/permissions.test.js +2051 -0
  146. package/dist/tests/permissions.test.js.map +1 -0
  147. package/dist/tests/priority.test.d.ts +2 -0
  148. package/dist/tests/priority.test.d.ts.map +1 -0
  149. package/dist/tests/priority.test.js +61 -0
  150. package/dist/tests/priority.test.js.map +1 -0
  151. package/dist/tests/sync.test.d.ts +2 -0
  152. package/dist/tests/sync.test.d.ts.map +1 -0
  153. package/dist/tests/sync.test.js +1548 -0
  154. package/dist/tests/sync.test.js.map +1 -0
  155. package/dist/tests/testUtils.d.ts +142 -0
  156. package/dist/tests/testUtils.d.ts.map +1 -0
  157. package/dist/tests/testUtils.js +315 -0
  158. package/dist/tests/testUtils.js.map +1 -0
  159. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts +4 -0
  160. package/dist/typeUtils/accountOrAgentIDfromSessionID.d.ts.map +1 -0
  161. package/dist/typeUtils/expectGroup.d.ts +4 -0
  162. package/dist/typeUtils/expectGroup.d.ts.map +1 -0
  163. package/dist/typeUtils/isAccountID.d.ts +4 -0
  164. package/dist/typeUtils/isAccountID.d.ts.map +1 -0
  165. package/dist/typeUtils/isCoValue.d.ts +4 -0
  166. package/dist/typeUtils/isCoValue.d.ts.map +1 -0
  167. package/dist/utils.d.ts +5 -0
  168. package/dist/utils.d.ts.map +1 -0
  169. package/package.json +6 -6
  170. package/src/coValueCore.ts +3 -9
  171. package/src/coValues/account.ts +15 -15
  172. package/src/coValues/group.ts +85 -12
  173. package/src/jsonValue.ts +9 -5
  174. package/src/localNode.ts +3 -5
  175. package/src/permissions.ts +5 -15
  176. package/src/tests/coValueCore.test.ts +2 -2
  177. package/src/tests/group.test.ts +187 -0
  178. package/src/tests/permissions.test.ts +330 -57
  179. package/src/tests/testUtils.ts +4 -1
  180. package/tsconfig.json +4 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isCoValue.d.ts","sourceRoot":"","sources":["../../src/typeUtils/isCoValue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAKhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GACxC,KAAK,IAAI,UAAU,CAOrB"}
@@ -0,0 +1,5 @@
1
+ export declare function parseError(e: unknown): {
2
+ message: string | null;
3
+ stack: string | null;
4
+ };
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAKA"}
package/package.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "name": "cojson",
3
3
  "module": "dist/index.js",
4
4
  "main": "dist/index.js",
5
- "types": "src/index.ts",
5
+ "types": "dist/index.d.ts",
6
6
  "exports": {
7
7
  ".": {
8
- "types": "./src/index.ts",
8
+ "types": "./dist/index.d.ts",
9
9
  "default": "./dist/index.js"
10
10
  },
11
11
  "./dist/crypto/PureJSCrypto": {
12
- "types": "./src/crypto/PureJSCrypto.ts",
12
+ "types": "./dist/crypto/PureJSCrypto.d.ts",
13
13
  "default": "./dist/crypto/PureJSCrypto.js"
14
14
  },
15
15
  "./crypto/PureJSCrypto": {
16
- "types": "./src/crypto/PureJSCrypto.ts",
16
+ "types": "./dist/crypto/PureJSCrypto.d.ts",
17
17
  "default": "./dist/crypto/PureJSCrypto.js"
18
18
  },
19
19
  "./crypto/WasmCrypto": {
20
- "types": "./src/crypto/WasmCrypto.ts",
20
+ "types": "./dist/crypto/WasmCrypto.d.ts",
21
21
  "default": "./dist/crypto/WasmCrypto.js"
22
22
  },
23
23
  "./dist/*": "./dist/*",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "type": "module",
27
27
  "license": "MIT",
28
- "version": "0.10.8",
28
+ "version": "0.11.0",
29
29
  "devDependencies": {
30
30
  "@opentelemetry/sdk-metrics": "^1.29.0",
31
31
  "typescript": "~5.6.2",
@@ -184,9 +184,7 @@ export class CoValueCore {
184
184
  this.header.meta?.type === "account"
185
185
  ? (this.node.currentSessionID.replace(
186
186
  this.node.account.id,
187
- this.node.account
188
- .currentAgentID()
189
- ._unsafeUnwrap({ withStackTrace: true }),
187
+ this.node.account.currentAgentID(),
190
188
  ) as SessionID)
191
189
  : this.node.currentSessionID;
192
190
 
@@ -455,9 +453,7 @@ export class CoValueCore {
455
453
  this.header.meta?.type === "account"
456
454
  ? (this.node.currentSessionID.replace(
457
455
  this.node.account.id,
458
- this.node.account
459
- .currentAgentID()
460
- ._unsafeUnwrap({ withStackTrace: true }),
456
+ this.node.account.currentAgentID(),
461
457
  ) as SessionID)
462
458
  : this.node.currentSessionID;
463
459
 
@@ -639,9 +635,7 @@ export class CoValueCore {
639
635
  // Try to find key revelation for us
640
636
  const lookupAccountOrAgentID =
641
637
  this.header.meta?.type === "account"
642
- ? this.node.account
643
- .currentAgentID()
644
- ._unsafeUnwrap({ withStackTrace: true })
638
+ ? this.node.account.currentAgentID()
645
639
  : this.node.account.id;
646
640
 
647
641
  const lastReadyKeyEdit = content.lastEditAt(
@@ -1,4 +1,3 @@
1
- import { Result, ok } from "neverthrow";
2
1
  import { CoID, RawCoValue } from "../coValue.js";
3
2
  import {
4
3
  CoValueCore,
@@ -47,10 +46,11 @@ export class RawAccount<
47
46
  > extends RawGroup<Meta> {
48
47
  _cachedCurrentAgentID: AgentID | undefined;
49
48
 
50
- currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
49
+ currentAgentID(): AgentID {
51
50
  if (this._cachedCurrentAgentID) {
52
- return ok(this._cachedCurrentAgentID);
51
+ return this._cachedCurrentAgentID;
53
52
  }
53
+
54
54
  const agents = this.keys()
55
55
  .filter((k): k is AgentID => k.startsWith("sealer_"))
56
56
  .sort(
@@ -65,7 +65,7 @@ export class RawAccount<
65
65
 
66
66
  this._cachedCurrentAgentID = agents[0];
67
67
 
68
- return ok(agents[0]!);
68
+ return agents[0]!;
69
69
  }
70
70
 
71
71
  createInvite(_: AccountRole): InviteSecret {
@@ -77,10 +77,10 @@ export interface ControlledAccountOrAgent {
77
77
  id: RawAccountID | AgentID;
78
78
  agentSecret: AgentSecret;
79
79
 
80
- currentAgentID: () => Result<AgentID, InvalidAccountAgentIDError>;
81
- currentSignerID: () => Result<SignerID, InvalidAccountAgentIDError>;
80
+ currentAgentID: () => AgentID;
81
+ currentSignerID: () => SignerID;
82
82
  currentSignerSecret: () => SignerSecret;
83
- currentSealerID: () => Result<SealerID, InvalidAccountAgentIDError>;
83
+ currentSealerID: () => SealerID;
84
84
  currentSealerSecret: () => SealerSecret;
85
85
  }
86
86
 
@@ -116,17 +116,17 @@ export class RawControlledAccount<Meta extends AccountMeta = AccountMeta>
116
116
  return this.core.node.acceptInvite(groupOrOwnedValueID, inviteSecret);
117
117
  }
118
118
 
119
- currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
119
+ currentAgentID(): AgentID {
120
120
  if (this._cachedCurrentAgentID) {
121
- return ok(this._cachedCurrentAgentID);
121
+ return this._cachedCurrentAgentID;
122
122
  }
123
123
  const agentID = this.crypto.getAgentID(this.agentSecret);
124
124
  this._cachedCurrentAgentID = agentID;
125
- return ok(agentID);
125
+ return agentID;
126
126
  }
127
127
 
128
128
  currentSignerID() {
129
- return this.currentAgentID().map((id) => this.crypto.getAgentSignerID(id));
129
+ return this.crypto.getAgentSignerID(this.currentAgentID());
130
130
  }
131
131
 
132
132
  currentSignerSecret(): SignerSecret {
@@ -134,7 +134,7 @@ export class RawControlledAccount<Meta extends AccountMeta = AccountMeta>
134
134
  }
135
135
 
136
136
  currentSealerID() {
137
- return this.currentAgentID().map((id) => this.crypto.getAgentSealerID(id));
137
+ return this.crypto.getAgentSealerID(this.currentAgentID());
138
138
  }
139
139
 
140
140
  currentSealerSecret(): SealerSecret {
@@ -153,11 +153,11 @@ export class ControlledAgent implements ControlledAccountOrAgent {
153
153
  }
154
154
 
155
155
  currentAgentID() {
156
- return ok(this.crypto.getAgentID(this.agentSecret));
156
+ return this.crypto.getAgentID(this.agentSecret);
157
157
  }
158
158
 
159
159
  currentSignerID() {
160
- return this.currentAgentID().map((id) => this.crypto.getAgentSignerID(id));
160
+ return this.crypto.getAgentSignerID(this.currentAgentID());
161
161
  }
162
162
 
163
163
  currentSignerSecret(): SignerSecret {
@@ -165,7 +165,7 @@ export class ControlledAgent implements ControlledAccountOrAgent {
165
165
  }
166
166
 
167
167
  currentSealerID() {
168
- return this.currentAgentID().map((id) => this.crypto.getAgentSealerID(id));
168
+ return this.crypto.getAgentSealerID(this.currentAgentID());
169
169
  }
170
170
 
171
171
  currentSealerSecret(): SealerSecret {
@@ -29,7 +29,12 @@ import { RawBinaryCoStream, RawCoStream } from "./coStream.js";
29
29
  export const EVERYONE = "everyone" as const;
30
30
  export type Everyone = "everyone";
31
31
 
32
- export type ParentGroupReferenceRole = "extend" | "reader" | "writer" | "admin";
32
+ export type ParentGroupReferenceRole =
33
+ | "revoked"
34
+ | "extend"
35
+ | "reader"
36
+ | "writer"
37
+ | "admin";
33
38
 
34
39
  export type GroupShape = {
35
40
  profile: CoID<RawCoMap> | null;
@@ -45,7 +50,7 @@ export type GroupShape = {
45
50
  { encryptedID: KeyID; encryptingID: KeyID }
46
51
  >;
47
52
  [parent: ParentGroupReference]: ParentGroupReferenceRole;
48
- [child: ChildGroupReference]: "extend";
53
+ [child: ChildGroupReference]: "revoked" | "extend";
49
54
  };
50
55
 
51
56
  /** A `Group` is a scope for permissions of its members (`"reader" | "writer" | "admin"`), applying to objects owned by that group.
@@ -77,7 +82,7 @@ export class RawGroup<
77
82
  *
78
83
  * @category 1. Role reading
79
84
  */
80
- roleOf(accountID: RawAccountID): Role | undefined {
85
+ roleOf(accountID: RawAccountID | typeof EVERYONE): Role | undefined {
81
86
  return this.roleOfInternal(accountID)?.role;
82
87
  }
83
88
 
@@ -85,15 +90,15 @@ export class RawGroup<
85
90
  roleOfInternal(
86
91
  accountID: RawAccountID | AgentID | typeof EVERYONE,
87
92
  ): { role: Role; via: CoID<RawGroup> | undefined } | undefined {
88
- const roleHere = this.get(accountID);
93
+ let roleHere = this.get(accountID);
89
94
 
90
95
  if (roleHere === "revoked") {
91
- return undefined;
96
+ roleHere = undefined;
92
97
  }
93
98
 
94
99
  let roleInfo:
95
100
  | {
96
- role: Exclude<Role, "revoked">;
101
+ role: Role;
97
102
  via: CoID<RawGroup> | undefined;
98
103
  }
99
104
  | undefined = roleHere && { role: roleHere, via: undefined };
@@ -114,6 +119,12 @@ export class RawGroup<
114
119
  }
115
120
  }
116
121
 
122
+ if (!roleInfo && accountID !== "everyone") {
123
+ const everyoneRole = this.get("everyone");
124
+
125
+ if (everyoneRole) return { role: everyoneRole, via: undefined };
126
+ }
127
+
117
128
  return roleInfo;
118
129
  }
119
130
 
@@ -122,6 +133,11 @@ export class RawGroup<
122
133
 
123
134
  for (const key of this.keys()) {
124
135
  if (isParentGroupReference(key)) {
136
+ // Check if the parent group reference is revoked
137
+ if (this.get(key) === "revoked") {
138
+ continue;
139
+ }
140
+
125
141
  const parent = this.core.node.expectCoValueLoaded(
126
142
  getParentGroupId(key),
127
143
  "Expected parent group to be loaded",
@@ -183,6 +199,11 @@ export class RawGroup<
183
199
 
184
200
  for (const key of this.keys()) {
185
201
  if (isChildGroupReference(key)) {
202
+ // Check if the child group reference is revoked
203
+ if (this.get(key) === "revoked") {
204
+ continue;
205
+ }
206
+
186
207
  const child = this.core.node.expectCoValueLoaded(
187
208
  getChildGroupId(key),
188
209
  "Expected child group to be loaded",
@@ -251,9 +272,7 @@ export class RawGroup<
251
272
 
252
273
  const memberKey = typeof account === "string" ? account : account.id;
253
274
  const agent =
254
- typeof account === "string"
255
- ? account
256
- : account.currentAgentID()._unsafeUnwrap({ withStackTrace: true });
275
+ typeof account === "string" ? account : account.currentAgentID();
257
276
 
258
277
  /**
259
278
  * WriteOnly members can only see their own changes.
@@ -387,6 +406,18 @@ export class RawGroup<
387
406
  });
388
407
  }
389
408
 
409
+ getAllMemberKeysSet() {
410
+ const memberKeys = new Set(this.getMemberKeys());
411
+
412
+ for (const { group } of this.getParentGroups()) {
413
+ for (const key of group.getAllMemberKeysSet()) {
414
+ memberKeys.add(key);
415
+ }
416
+ }
417
+
418
+ return memberKeys;
419
+ }
420
+
390
421
  /** @internal */
391
422
  rotateReadKey(removedMemberKey?: RawAccountID | AgentID | "everyone") {
392
423
  const memberKeys = this.getMemberKeys().filter(
@@ -608,6 +639,43 @@ export class RawGroup<
608
639
  );
609
640
  }
610
641
 
642
+ async revokeExtend(parent: RawGroup) {
643
+ if (this.myRole() !== "admin") {
644
+ throw new Error(
645
+ "To unextend a group, the current account must be an admin in the child group",
646
+ );
647
+ }
648
+
649
+ if (
650
+ parent.myRole() !== "admin" &&
651
+ parent.myRole() !== "writer" &&
652
+ parent.myRole() !== "reader" &&
653
+ parent.myRole() !== "writeOnly"
654
+ ) {
655
+ throw new Error(
656
+ "To unextend a group, the current account must be a member of the parent group",
657
+ );
658
+ }
659
+
660
+ if (
661
+ !this.get(`parent_${parent.id}`) ||
662
+ this.get(`parent_${parent.id}`) === "revoked"
663
+ ) {
664
+ return;
665
+ }
666
+
667
+ // Set the parent key on the child group to `revoked`
668
+ this.set(`parent_${parent.id}`, "revoked", "trusting");
669
+
670
+ // Set the child key on the parent group to `revoked`
671
+ parent.set(`child_${this.id}`, "revoked", "trusting");
672
+
673
+ await this.loadAllChildGroups();
674
+
675
+ // Rotate the keys on the child group
676
+ this.rotateReadKey();
677
+ }
678
+
611
679
  /**
612
680
  * Strips the specified member of all roles (preventing future writes in
613
681
  * the group and owned values) and rotates the read encryption key for that group
@@ -783,8 +851,9 @@ export class RawGroup<
783
851
 
784
852
  export function isInheritableRole(
785
853
  roleInParent: Role | undefined,
786
- ): roleInParent is "admin" | "writer" | "reader" {
854
+ ): roleInParent is "revoked" | "admin" | "writer" | "reader" {
787
855
  return (
856
+ roleInParent === "revoked" ||
788
857
  roleInParent === "admin" ||
789
858
  roleInParent === "writer" ||
790
859
  roleInParent === "reader"
@@ -792,9 +861,13 @@ export function isInheritableRole(
792
861
  }
793
862
 
794
863
  function isMorePermissiveAndShouldInherit(
795
- roleInParent: "admin" | "writer" | "reader",
796
- roleInChild: Exclude<Role, "revoked"> | undefined,
864
+ roleInParent: "revoked" | "admin" | "writer" | "reader",
865
+ roleInChild: Role | undefined,
797
866
  ) {
867
+ if (roleInParent === "revoked") {
868
+ return true;
869
+ }
870
+
798
871
  if (roleInParent === "admin") {
799
872
  return !roleInChild || roleInChild !== "admin";
800
873
  }
package/src/jsonValue.ts CHANGED
@@ -8,11 +8,15 @@ export type JsonObject = { [key: string]: JsonValue | undefined };
8
8
  type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> &
9
9
  U[keyof U];
10
10
  type ExcludeEmpty<T> = T extends AtLeastOne<T> ? T : never;
11
+ type ExcludeNull<T> = T extends null ? never : T;
12
+ type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
13
+ type ContainsSymbolKeys<T> = keyof ExcludeNull<T> extends symbol ? true : false;
11
14
 
12
- export type CoJsonValue<T> =
13
- | JsonValue
14
- | CoJsonObjectWithIndex<T>
15
- | CoJsonArray<T>;
15
+ export type CoJsonValue<T> = IsFunction<T> extends true
16
+ ? "Functions are not allowed"
17
+ : ContainsSymbolKeys<T> extends true
18
+ ? "Only string or number keys are allowed"
19
+ : JsonValue | CoJsonObjectWithIndex<T> | CoJsonArray<T>;
16
20
  export type CoJsonArray<T> = CoJsonValue<T>[] | readonly CoJsonValue<T>[];
17
21
 
18
22
  /**
@@ -25,7 +29,7 @@ export type CoJsonArray<T> = CoJsonValue<T>[] | readonly CoJsonValue<T>[];
25
29
  * Applying the ExcludeEmpty type here to make sure we don't accept functions or non-serializable values
26
30
  */
27
31
  export type CoJsonObjectWithIndex<T> = ExcludeEmpty<{
28
- [K in keyof T & string]: CoJsonValue1L<T[K]> | undefined;
32
+ [K in keyof T]: CoJsonValue1L<T[K]> | undefined;
29
33
  }>;
30
34
 
31
35
  /**
package/src/localNode.ts CHANGED
@@ -530,7 +530,7 @@ export class LocalNode {
530
530
  } satisfies UnexpectedlyNotAccountError);
531
531
  }
532
532
 
533
- return (coValue.getCurrentContent() as RawAccount).currentAgentID();
533
+ return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
534
534
  }
535
535
 
536
536
  resolveAccountAgentAsync(
@@ -573,7 +573,7 @@ export class LocalNode {
573
573
  } satisfies UnexpectedlyNotAccountError);
574
574
  }
575
575
 
576
- return (coValue.getCurrentContent() as RawAccount).currentAgentID();
576
+ return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
577
577
  });
578
578
  }
579
579
 
@@ -601,9 +601,7 @@ export class LocalNode {
601
601
  this.crypto.seal({
602
602
  message: readKey.secret,
603
603
  from: this.account.currentSealerSecret(),
604
- to: this.account
605
- .currentSealerID()
606
- ._unsafeUnwrap({ withStackTrace: true }),
604
+ to: this.account.currentSealerID(),
607
605
  nOnceMaterial: {
608
606
  in: groupCoValue.id,
609
607
  tx: groupCoValue.nextTransactionID(),
@@ -56,7 +56,7 @@ function logPermissionError(
56
56
  return;
57
57
  }
58
58
 
59
- logger.warn("Permission error: " + message, attributes);
59
+ logger.debug("Permission error: " + message, attributes);
60
60
  }
61
61
 
62
62
  export function determineValidTransactions(
@@ -101,8 +101,7 @@ export function determineValidTransactions(
101
101
  }
102
102
 
103
103
  const transactorRoleAtTxTime =
104
- groupAtTime.roleOfInternal(effectiveTransactor)?.role ||
105
- groupAtTime.roleOfInternal(EVERYONE)?.role;
104
+ groupAtTime.roleOfInternal(effectiveTransactor)?.role;
106
105
 
107
106
  if (
108
107
  transactorRoleAtTxTime !== "admin" &&
@@ -135,8 +134,8 @@ export function determineValidTransactions(
135
134
  }
136
135
 
137
136
  function isHigherRole(a: Role, b: Role | undefined) {
138
- if (a === undefined) return false;
139
- if (b === undefined) return true;
137
+ if (a === undefined || a === "revoked") return false;
138
+ if (b === undefined || b === "revoked") return true;
140
139
  if (b === "admin") return false;
141
140
  if (a === "admin") return true;
142
141
 
@@ -476,16 +475,7 @@ function agentInAccountOrMemberInGroup(
476
475
  groupAtTime: RawGroup,
477
476
  ): RawAccountID | AgentID | undefined {
478
477
  if (transactor === groupAtTime.id && groupAtTime instanceof RawAccount) {
479
- return groupAtTime.currentAgentID().match(
480
- (agentID) => agentID,
481
- (e) => {
482
- logger.error(
483
- "Error while determining current agent ID in valid transactions",
484
- e,
485
- );
486
- return undefined;
487
- },
488
- );
478
+ return groupAtTime.currentAgentID();
489
479
  }
490
480
  return transactor;
491
481
  }
@@ -155,7 +155,7 @@ test("New transactions in a group correctly update owned values, including subsc
155
155
 
156
156
  map.subscribe(listener);
157
157
 
158
- expect(listener.mock.calls[0][0].get("hello")).toBe("world");
158
+ expect(listener.mock.calls[0]?.[0].get("hello")).toBe("world");
159
159
 
160
160
  const resignationThatWeJustLearnedAbout = {
161
161
  privacy: "trusting",
@@ -192,7 +192,7 @@ test("New transactions in a group correctly update owned values, including subsc
192
192
  expect(manuallyAdddedTxSuccess).toBe(true);
193
193
 
194
194
  expect(listener.mock.calls.length).toBe(2);
195
- expect(listener.mock.calls[1][0].get("hello")).toBe(undefined);
195
+ expect(listener.mock.calls[1]?.[0].get("hello")).toBe(undefined);
196
196
 
197
197
  expect(map.core.getValidSortedTransactions().length).toBe(0);
198
198
  });
@@ -657,6 +657,79 @@ describe("extend", () => {
657
657
  });
658
658
  });
659
659
 
660
+ describe("unextend", () => {
661
+ test("should revoke roles", async () => {
662
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
663
+ "server",
664
+ "server",
665
+ "server",
666
+ );
667
+
668
+ // `parentGroup` has `alice` as a writer
669
+ const parentGroup = node1.node.createGroup();
670
+ const alice = await loadCoValueOrFail(node1.node, node2.accountID);
671
+ parentGroup.addMember(alice, "writer");
672
+ // `alice`'s role in `parentGroup` is `"writer"`
673
+ expect(parentGroup.roleOf(alice.id)).toBe("writer");
674
+
675
+ // `childGroup` has `bob` as a reader
676
+ const childGroup = node1.node.createGroup();
677
+ const bob = await loadCoValueOrFail(node1.node, node3.accountID);
678
+ childGroup.addMember(bob, "reader");
679
+ // `bob`'s role in `childGroup` is `"reader"`
680
+ expect(childGroup.roleOf(bob.id)).toBe("reader");
681
+
682
+ // `childGroup` has `parentGroup`'s members (in this case, `alice` as a writer)
683
+ childGroup.extend(parentGroup);
684
+ expect(childGroup.roleOf(alice.id)).toBe("writer");
685
+
686
+ // `childGroup` no longer has `parentGroup`'s members
687
+ await childGroup.revokeExtend(parentGroup);
688
+ expect(childGroup.roleOf(bob.id)).toBe("reader");
689
+ expect(childGroup.roleOf(alice.id)).toBe(undefined);
690
+ });
691
+
692
+ test("should do nothing if applied to a group that is not extended", async () => {
693
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
694
+ "server",
695
+ "server",
696
+ "server",
697
+ );
698
+
699
+ const parentGroup = node1.node.createGroup();
700
+ const alice = await loadCoValueOrFail(node1.node, node2.accountID);
701
+ parentGroup.addMember(alice, "writer");
702
+ const childGroup = node1.node.createGroup();
703
+ const bob = await loadCoValueOrFail(node1.node, node3.accountID);
704
+ childGroup.addMember(bob, "reader");
705
+ await childGroup.revokeExtend(parentGroup);
706
+ expect(childGroup.roleOf(bob.id)).toBe("reader");
707
+ expect(childGroup.roleOf(alice.id)).toBe(undefined);
708
+ });
709
+
710
+ test("should not throw if the revokeExtend is called twice", async () => {
711
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
712
+ "server",
713
+ "server",
714
+ "server",
715
+ );
716
+
717
+ const parentGroup = node1.node.createGroup();
718
+ const alice = await loadCoValueOrFail(node1.node, node2.accountID);
719
+ parentGroup.addMember(alice, "writer");
720
+ const childGroup = node1.node.createGroup();
721
+ const bob = await loadCoValueOrFail(node1.node, node3.accountID);
722
+ childGroup.addMember(bob, "reader");
723
+
724
+ childGroup.extend(parentGroup);
725
+
726
+ await childGroup.revokeExtend(parentGroup);
727
+ await childGroup.revokeExtend(parentGroup);
728
+ expect(childGroup.roleOf(bob.id)).toBe("reader");
729
+ expect(childGroup.roleOf(alice.id)).toBe(undefined);
730
+ });
731
+ });
732
+
660
733
  describe("extend with role mapping", () => {
661
734
  test("mapping to writer should add the ability to write", async () => {
662
735
  const { node1, node2 } = await createTwoConnectedNodes("server", "server");
@@ -842,3 +915,117 @@ describe("extend with role mapping", () => {
842
915
  expect(mapOnNode2.get("test")).toEqual("Written from the admin");
843
916
  });
844
917
  });
918
+ test("roleOf should prioritize explicit account role over everyone role in same group", async () => {
919
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
920
+
921
+ const group = node1.node.createGroup();
922
+ const account2 = await loadCoValueOrFail(node1.node, node2.accountID);
923
+
924
+ // Add both everyone and specific account
925
+ group.addMember("everyone", "reader");
926
+ group.addMember(account2, "writer");
927
+
928
+ // Should return the explicit role, not everyone's role
929
+ expect(group.roleOf(node2.accountID)).toEqual("writer");
930
+
931
+ // Change everyone's role
932
+ group.addMember("everyone", "writer");
933
+
934
+ // Should still return the explicit role
935
+ expect(group.roleOf(node2.accountID)).toEqual("writer");
936
+ });
937
+
938
+ test("roleOf should prioritize inherited everyone role over explicit account role", async () => {
939
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
940
+
941
+ const parentGroup = node1.node.createGroup();
942
+ const childGroup = node1.node.createGroup();
943
+ const account2 = await loadCoValueOrFail(node1.node, node2.accountID);
944
+
945
+ // Set up inheritance
946
+ childGroup.extend(parentGroup);
947
+
948
+ // Add everyone to parent and account to child
949
+ parentGroup.addMember("everyone", "writer");
950
+ childGroup.addMember(account2, "reader");
951
+
952
+ // Should return the explicit role from child, not inherited everyone role
953
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
954
+ });
955
+
956
+ test("roleOf should use everyone role when no explicit role exists", async () => {
957
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
958
+
959
+ const group = node1.node.createGroup();
960
+
961
+ // Add only everyone role
962
+ group.addMember("everyone", "reader");
963
+
964
+ // Should return everyone's role when no explicit role exists
965
+ expect(group.roleOf(node2.accountID)).toEqual("reader");
966
+ });
967
+
968
+ test("roleOf should inherit everyone role from parent when no explicit roles exist", async () => {
969
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
970
+
971
+ const parentGroup = node1.node.createGroup();
972
+ const childGroup = node1.node.createGroup();
973
+
974
+ // Set up inheritance
975
+ childGroup.extend(parentGroup);
976
+
977
+ // Add everyone to parent only
978
+ parentGroup.addMember("everyone", "reader");
979
+
980
+ // Should inherit everyone's role from parent
981
+ expect(childGroup.roleOf(node2.accountID)).toEqual("reader");
982
+ });
983
+
984
+ test("roleOf should handle everyone role inheritance through multiple levels", async () => {
985
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
986
+
987
+ const grandParentGroup = node1.node.createGroup();
988
+ const parentGroup = node1.node.createGroup();
989
+ const childGroup = node1.node.createGroup();
990
+
991
+ const childGroupOnNode2 = await loadCoValueOrFail(node2.node, childGroup.id);
992
+
993
+ const account2 = await loadCoValueOrFail(node1.node, node2.accountID);
994
+
995
+ // Set up inheritance chain
996
+ parentGroup.extend(grandParentGroup);
997
+ childGroup.extend(parentGroup);
998
+
999
+ // Add everyone to grandparent
1000
+ grandParentGroup.addMember("everyone", "writer");
1001
+
1002
+ // Should inherit everyone's role from grandparent
1003
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
1004
+
1005
+ // Add explicit role in parent
1006
+ parentGroup.addMember(account2, "reader");
1007
+
1008
+ // Should use parent's explicit role instead of grandparent's everyone role
1009
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
1010
+
1011
+ // Add explicit role in child
1012
+ childGroup.addMember(account2, "admin");
1013
+ await childGroup.core.waitForSync();
1014
+
1015
+ // Should use child's explicit role
1016
+ expect(childGroup.roleOf(node2.accountID)).toEqual("admin");
1017
+
1018
+ // Remove child's explicit role
1019
+ await childGroupOnNode2.removeMember(account2);
1020
+ await childGroupOnNode2.core.waitForSync();
1021
+
1022
+ // Should fall back to parent's explicit role
1023
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
1024
+
1025
+ // Remove parent's explicit role
1026
+ await parentGroup.removeMember(account2);
1027
+ await childGroup.core.waitForSync();
1028
+
1029
+ // Should fall back to grandparent's everyone role
1030
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
1031
+ });