jazz-tools 0.10.14 → 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 (150) hide show
  1. package/.turbo/turbo-build.log +11 -7
  2. package/CHANGELOG.md +31 -0
  3. package/dist/auth/AuthSecretStorage.d.ts +25 -0
  4. package/dist/auth/AuthSecretStorage.d.ts.map +1 -0
  5. package/dist/auth/DemoAuth.d.ts +27 -0
  6. package/dist/auth/DemoAuth.d.ts.map +1 -0
  7. package/dist/auth/InMemoryKVStore.d.ts +9 -0
  8. package/dist/auth/InMemoryKVStore.d.ts.map +1 -0
  9. package/dist/auth/KvStoreContext.d.ts +17 -0
  10. package/dist/auth/KvStoreContext.d.ts.map +1 -0
  11. package/dist/auth/PassphraseAuth.d.ts +35 -0
  12. package/dist/auth/PassphraseAuth.d.ts.map +1 -0
  13. package/dist/{chunk-5YDDEUNX.js → chunk-RTRX7HIO.js} +193 -81
  14. package/dist/chunk-RTRX7HIO.js.map +1 -0
  15. package/dist/coValues/account.d.ts +120 -0
  16. package/dist/coValues/account.d.ts.map +1 -0
  17. package/dist/coValues/coFeed.d.ts +361 -0
  18. package/dist/coValues/coFeed.d.ts.map +1 -0
  19. package/dist/coValues/coList.d.ts +221 -0
  20. package/dist/coValues/coList.d.ts.map +1 -0
  21. package/dist/coValues/coMap.d.ts +500 -0
  22. package/dist/coValues/coMap.d.ts.map +1 -0
  23. package/dist/coValues/coPlainText.d.ts +69 -0
  24. package/dist/coValues/coPlainText.d.ts.map +1 -0
  25. package/dist/coValues/coRichText.d.ts +259 -0
  26. package/dist/coValues/coRichText.d.ts.map +1 -0
  27. package/dist/coValues/deepLoading.d.ts +81 -0
  28. package/dist/coValues/deepLoading.d.ts.map +1 -0
  29. package/dist/coValues/extensions/imageDef.d.ts +17 -0
  30. package/dist/coValues/extensions/imageDef.d.ts.map +1 -0
  31. package/dist/coValues/group.d.ts +67 -0
  32. package/dist/coValues/group.d.ts.map +1 -0
  33. package/dist/coValues/inbox.d.ts +52 -0
  34. package/dist/coValues/inbox.d.ts.map +1 -0
  35. package/dist/coValues/interfaces.d.ts +97 -0
  36. package/dist/coValues/interfaces.d.ts.map +1 -0
  37. package/dist/coValues/profile.d.ts +28 -0
  38. package/dist/coValues/profile.d.ts.map +1 -0
  39. package/dist/coValues/registeredSchemas.d.ts +12 -0
  40. package/dist/coValues/registeredSchemas.d.ts.map +1 -0
  41. package/dist/coValues/schemaUnion.d.ts +79 -0
  42. package/dist/coValues/schemaUnion.d.ts.map +1 -0
  43. package/dist/exports.d.ts +27 -0
  44. package/dist/exports.d.ts.map +1 -0
  45. package/dist/implementation/ContextManager.d.ts +65 -0
  46. package/dist/implementation/ContextManager.d.ts.map +1 -0
  47. package/dist/implementation/activeAccountContext.d.ts +12 -0
  48. package/dist/implementation/activeAccountContext.d.ts.map +1 -0
  49. package/dist/implementation/anonymousJazzAgent.d.ts +7 -0
  50. package/dist/implementation/anonymousJazzAgent.d.ts.map +1 -0
  51. package/dist/implementation/createContext.d.ts +91 -0
  52. package/dist/implementation/createContext.d.ts.map +1 -0
  53. package/dist/implementation/devtoolsFormatters.d.ts +2 -0
  54. package/dist/implementation/devtoolsFormatters.d.ts.map +1 -0
  55. package/dist/implementation/errors.d.ts +2 -0
  56. package/dist/implementation/errors.d.ts.map +1 -0
  57. package/dist/implementation/inspect.d.ts +3 -0
  58. package/dist/implementation/inspect.d.ts.map +1 -0
  59. package/dist/implementation/invites.d.ts +23 -0
  60. package/dist/implementation/invites.d.ts.map +1 -0
  61. package/dist/implementation/refs.d.ts +21 -0
  62. package/dist/implementation/refs.d.ts.map +1 -0
  63. package/dist/implementation/schema.d.ts +72 -0
  64. package/dist/implementation/schema.d.ts.map +1 -0
  65. package/dist/implementation/subscriptionScope.d.ts +33 -0
  66. package/dist/implementation/subscriptionScope.d.ts.map +1 -0
  67. package/dist/implementation/symbols.d.ts +8 -0
  68. package/dist/implementation/symbols.d.ts.map +1 -0
  69. package/dist/index.d.ts +3 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +1 -1
  72. package/dist/internal.d.ts +12 -0
  73. package/dist/internal.d.ts.map +1 -0
  74. package/dist/lib/cache.d.ts +6 -0
  75. package/dist/lib/cache.d.ts.map +1 -0
  76. package/dist/lib/cache.test.d.ts +2 -0
  77. package/dist/lib/cache.test.d.ts.map +1 -0
  78. package/dist/testing.d.ts +41 -0
  79. package/dist/testing.d.ts.map +1 -0
  80. package/dist/testing.js +11 -16
  81. package/dist/testing.js.map +1 -1
  82. package/dist/tests/AuthSecretStorage.test.d.ts +2 -0
  83. package/dist/tests/AuthSecretStorage.test.d.ts.map +1 -0
  84. package/dist/tests/ContextManager.test.d.ts +2 -0
  85. package/dist/tests/ContextManager.test.d.ts.map +1 -0
  86. package/dist/tests/DemoAuth.test.d.ts +2 -0
  87. package/dist/tests/DemoAuth.test.d.ts.map +1 -0
  88. package/dist/tests/PassphraseAuth.test.d.ts +2 -0
  89. package/dist/tests/PassphraseAuth.test.d.ts.map +1 -0
  90. package/dist/tests/account.test.d.ts +2 -0
  91. package/dist/tests/account.test.d.ts.map +1 -0
  92. package/dist/tests/coFeed.test.d.ts +2 -0
  93. package/dist/tests/coFeed.test.d.ts.map +1 -0
  94. package/dist/tests/coList.test.d.ts +2 -0
  95. package/dist/tests/coList.test.d.ts.map +1 -0
  96. package/dist/tests/coMap.test.d.ts +2 -0
  97. package/dist/tests/coMap.test.d.ts.map +1 -0
  98. package/dist/tests/coPlainText.test.d.ts +2 -0
  99. package/dist/tests/coPlainText.test.d.ts.map +1 -0
  100. package/dist/tests/coRichText.test.d.ts +2 -0
  101. package/dist/tests/coRichText.test.d.ts.map +1 -0
  102. package/dist/tests/createContext.test.d.ts +2 -0
  103. package/dist/tests/createContext.test.d.ts.map +1 -0
  104. package/dist/tests/deepLoading.test.d.ts +2 -0
  105. package/dist/tests/deepLoading.test.d.ts.map +1 -0
  106. package/dist/tests/fixtures.d.ts +2 -0
  107. package/dist/tests/fixtures.d.ts.map +1 -0
  108. package/dist/tests/groupsAndAccounts.test.d.ts +2 -0
  109. package/dist/tests/groupsAndAccounts.test.d.ts.map +1 -0
  110. package/dist/tests/inbox.test.d.ts +2 -0
  111. package/dist/tests/inbox.test.d.ts.map +1 -0
  112. package/dist/tests/interfaces.test.d.ts +2 -0
  113. package/dist/tests/interfaces.test.d.ts.map +1 -0
  114. package/dist/tests/invites.test.d.ts +2 -0
  115. package/dist/tests/invites.test.d.ts.map +1 -0
  116. package/dist/tests/schema.test.d.ts +2 -0
  117. package/dist/tests/schema.test.d.ts.map +1 -0
  118. package/dist/tests/schemaUnion.test.d.ts +2 -0
  119. package/dist/tests/schemaUnion.test.d.ts.map +1 -0
  120. package/dist/tests/subscribe.test.d.ts +2 -0
  121. package/dist/tests/subscribe.test.d.ts.map +1 -0
  122. package/dist/tests/testing.test.d.ts +2 -0
  123. package/dist/tests/testing.test.d.ts.map +1 -0
  124. package/dist/tests/utils.d.ts +21 -0
  125. package/dist/tests/utils.d.ts.map +1 -0
  126. package/dist/types.d.ts +52 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/package.json +8 -7
  129. package/src/coValues/account.ts +69 -11
  130. package/src/coValues/coMap.ts +2 -2
  131. package/src/coValues/coRichText.ts +42 -17
  132. package/src/coValues/group.ts +76 -31
  133. package/src/coValues/inbox.ts +10 -0
  134. package/src/coValues/interfaces.ts +1 -1
  135. package/src/coValues/profile.ts +35 -2
  136. package/src/implementation/ContextManager.ts +63 -15
  137. package/src/implementation/schema.ts +1 -3
  138. package/src/testing.ts +10 -16
  139. package/src/tests/AuthSecretStorage.test.ts +1 -2
  140. package/src/tests/ContextManager.test.ts +27 -14
  141. package/src/tests/PassphraseAuth.test.ts +7 -3
  142. package/src/tests/coMap.test.ts +20 -21
  143. package/src/tests/deepLoading.test.ts +8 -17
  144. package/src/tests/groupsAndAccounts.test.ts +429 -89
  145. package/src/tests/inbox.test.ts +24 -0
  146. package/src/tests/schema.test.ts +45 -5
  147. package/src/tests/utils.ts +7 -3
  148. package/src/types.ts +6 -0
  149. package/tsconfig.json +4 -1
  150. package/dist/chunk-5YDDEUNX.js.map +0 -1
@@ -6,6 +6,7 @@ import {
6
6
  CoFeed,
7
7
  CoList,
8
8
  CoMap,
9
+ Group,
9
10
  ID,
10
11
  Profile,
11
12
  SessionID,
@@ -187,17 +188,15 @@ class CustomAccount extends Account {
187
188
  creationProps?: { name: string } | undefined,
188
189
  ) {
189
190
  if (creationProps) {
191
+ const profileGroup = Group.create(this);
190
192
  this.profile = CustomProfile.create(
191
193
  {
192
194
  name: creationProps.name,
193
- stream: TestStream.create([], { owner: this }),
195
+ stream: TestStream.create([], this),
194
196
  },
195
- { owner: this },
196
- );
197
- this.root = TestMap.create(
198
- { list: TestList.create([], { owner: this }) },
199
- { owner: this },
197
+ profileGroup,
200
198
  );
199
+ this.root = TestMap.create({ list: TestList.create([], this) }, this);
201
200
  }
202
201
 
203
202
  const thisLoaded = await this.ensureLoaded({
@@ -312,19 +311,11 @@ test("doesn't break on Map.Record key deletion when the key is referenced in the
312
311
 
313
312
  class JazzySnapStore extends CoMap.Record(co.ref(JazzProfile)) {}
314
313
 
315
- const me = await Account.create({
316
- creationProps: { name: "Tester McTesterson" },
317
- crypto: Crypto,
314
+ const snapStore = JazzySnapStore.create({
315
+ profile1: JazzProfile.create({ firstName: "John" }),
316
+ profile2: JazzProfile.create({ firstName: "John" }),
318
317
  });
319
318
 
320
- const snapStore = JazzySnapStore.create(
321
- {
322
- profile1: JazzProfile.create({ firstName: "John" }, { owner: me }),
323
- profile2: JazzProfile.create({ firstName: "John" }, { owner: me }),
324
- },
325
- { owner: me },
326
- );
327
-
328
319
  const spy = vi.fn();
329
320
  const unsub = snapStore.subscribe({ profile1: {}, profile2: {} }, spy);
330
321
 
@@ -1,97 +1,84 @@
1
1
  import { WasmCrypto } from "cojson/crypto/WasmCrypto";
2
- import { beforeEach, describe, expect, test } from "vitest";
2
+ import { assert, beforeEach, describe, expect, test } from "vitest";
3
3
  import { Account, CoMap, Group, Profile, co } from "../exports.js";
4
- import { createJazzTestAccount } from "../testing.js";
4
+ import { Ref } from "../internal.js";
5
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
5
6
  import { setupTwoNodes } from "./utils.js";
6
7
 
7
8
  const Crypto = await WasmCrypto.create();
8
9
 
9
10
  beforeEach(async () => {
11
+ await setupJazzTestSync();
12
+
10
13
  await createJazzTestAccount({
11
14
  isCurrentActiveAccount: true,
12
15
  });
13
16
  });
14
17
 
15
18
  describe("Custom accounts and groups", async () => {
16
- class CustomProfile extends Profile {
17
- name = co.string;
18
- color = co.string;
19
- }
20
-
21
- class CustomAccount extends Account {
22
- profile = co.ref(CustomProfile);
23
- root = co.ref(CoMap);
24
-
25
- migrate(this: CustomAccount, creationProps?: { name: string }) {
26
- if (creationProps) {
27
- const profileGroup = Group.create({ owner: this });
28
- profileGroup.addMember("everyone", "reader");
29
- this.profile = CustomProfile.create(
30
- { name: creationProps.name, color: "blue" },
31
- { owner: this },
32
- );
33
- }
19
+ test("Custom account and group", async () => {
20
+ class CustomProfile extends Profile {
21
+ name = co.string;
22
+ color = co.string;
34
23
  }
35
- }
36
-
37
- class CustomGroup extends Group {
38
- profile = co.null;
39
- root = co.null;
40
- [co.members] = co.ref(CustomAccount);
41
24
 
42
- get nMembers() {
43
- return this.members.length;
25
+ class CustomAccount extends Account {
26
+ profile = co.ref(CustomProfile);
27
+ root = co.ref(CoMap);
28
+
29
+ migrate(this: CustomAccount, creationProps?: { name: string }) {
30
+ if (creationProps) {
31
+ const profileGroup = Group.create({ owner: this });
32
+ profileGroup.addMember("everyone", "reader");
33
+ this.profile = CustomProfile.create(
34
+ { name: creationProps.name, color: "blue" },
35
+ profileGroup,
36
+ );
37
+ }
38
+ }
44
39
  }
45
- }
46
40
 
47
- test("Custom account and group", async () => {
48
- const me = await CustomAccount.create({
41
+ const me = await createJazzTestAccount({
49
42
  creationProps: { name: "Hermes Puggington" },
50
- crypto: Crypto,
43
+ isCurrentActiveAccount: true,
44
+ AccountSchema: CustomAccount,
51
45
  });
52
46
 
53
47
  expect(me.profile).toBeDefined();
54
48
  expect(me.profile?.name).toBe("Hermes Puggington");
55
49
  expect(me.profile?.color).toBe("blue");
56
50
 
57
- const group = new CustomGroup({ owner: me });
51
+ const group = Group.create({ owner: me });
58
52
  group.addMember("everyone", "reader");
59
53
 
60
- expect(group.members).toMatchObject([
61
- { id: me.id, role: "admin" },
62
- { id: "everyone", role: "reader" },
63
- ]);
54
+ expect(group.members).toMatchObject([{ id: me.id, role: "admin" }]);
64
55
 
65
- expect(group.nMembers).toBe(2);
66
-
67
- await new Promise<void>((resolve) => {
68
- group.subscribe({}, (update) => {
69
- const meAsMember = update.members.find((member) => {
70
- return member.id === me.id && member.account?.profile;
71
- });
72
- if (meAsMember) {
73
- expect(meAsMember.account?.profile?.name).toBe("Hermes Puggington");
74
- expect(meAsMember.account?.profile?.color).toBe("blue");
75
- resolve();
76
- }
77
- });
78
- });
56
+ const meAsMember = group.members.find((member) => member.id === me.id);
57
+ expect((meAsMember?.account as CustomAccount).profile?.name).toBe(
58
+ "Hermes Puggington",
59
+ );
60
+ expect((meAsMember?.account as CustomAccount).profile?.color).toBe("blue");
61
+ });
79
62
 
80
- class MyMap extends CoMap {
81
- name = co.string;
63
+ test("Should throw when creating a profile with an account as owner", async () => {
64
+ class CustomAccount extends Account {
65
+ migrate(this: CustomAccount, creationProps?: { name: string }) {
66
+ if (creationProps) {
67
+ this.profile = Profile.create(
68
+ { name: creationProps.name },
69
+ // @ts-expect-error - only groups can own profiles, but we want to also perform a runtime check
70
+ this,
71
+ );
72
+ }
73
+ }
82
74
  }
83
75
 
84
- const map = MyMap.create({ name: "test" }, { owner: group });
85
-
86
- const meAsCastMember = map._owner
87
- .castAs(CustomGroup)
88
- .members.find((member) => member.id === me.id);
89
- expect(meAsCastMember?.account?.profile?.name).toBe("Hermes Puggington");
90
- expect(meAsCastMember?.account?.profile?.color).toBe("blue");
91
-
92
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
- expect((map._owner as any).nMembers).toBeUndefined();
94
- expect(map._owner.castAs(CustomGroup).nMembers).toBe(2);
76
+ await expect(() =>
77
+ CustomAccount.create({
78
+ creationProps: { name: "Hermes Puggington" },
79
+ crypto: Crypto,
80
+ }),
81
+ ).rejects.toThrowError("Profiles should be owned by a group");
95
82
  });
96
83
  });
97
84
 
@@ -173,6 +160,42 @@ describe("Group inheritance", () => {
173
160
  expect(mapAsReaderAfterUpdate?.title).toBe("In Grand Child");
174
161
  });
175
162
 
163
+ test("Group.getParentGroups should return the parent groups", async () => {
164
+ const me = await Account.create({
165
+ creationProps: { name: "Test Owner" },
166
+ crypto: Crypto,
167
+ });
168
+
169
+ const grandParentGroup = Group.create({ owner: me });
170
+ const parentGroup = Group.create({ owner: me });
171
+ const childGroup = Group.create({ owner: me });
172
+
173
+ childGroup.extend(parentGroup);
174
+ parentGroup.extend(grandParentGroup);
175
+
176
+ const parentGroups = childGroup.getParentGroups();
177
+
178
+ expect(parentGroups).toHaveLength(1);
179
+ expect(parentGroups).toContainEqual(
180
+ expect.objectContaining({ id: parentGroup.id }),
181
+ );
182
+
183
+ expect(parentGroups[0]?.getParentGroups()).toContainEqual(
184
+ expect.objectContaining({ id: grandParentGroup.id }),
185
+ );
186
+ });
187
+
188
+ test("Account.getParentGroups should return an empty array", async () => {
189
+ const account = await Account.create({
190
+ creationProps: { name: "Test Account" },
191
+ crypto: Crypto,
192
+ });
193
+
194
+ const parentGroups = account.getParentGroups();
195
+
196
+ expect(parentGroups).toEqual([]);
197
+ });
198
+
176
199
  test("waitForSync should resolve when the value is uploaded", async () => {
177
200
  const { clientNode, serverNode, clientAccount } = await setupTwoNodes();
178
201
 
@@ -192,56 +215,373 @@ describe("Group inheritance", () => {
192
215
  const group = Group.create();
193
216
  group.addMember("everyone", "reader");
194
217
 
195
- expect(group.members).toContainEqual({
196
- id: "everyone",
197
- role: "reader",
198
- account: undefined,
199
- ref: undefined,
200
- });
218
+ expect(group.getRoleOf("everyone")).toBe("reader");
201
219
 
202
220
  group.addMember("everyone", "writer");
203
221
 
204
- expect(group.members).toContainEqual({
205
- id: "everyone",
206
- role: "writer",
207
- account: undefined,
208
- ref: undefined,
209
- });
222
+ expect(group.getRoleOf("everyone")).toBe("writer");
210
223
 
211
224
  // @ts-expect-error - admin is not a valid role for everyone
212
225
  expect(() => group.addMember("everyone", "admin")).toThrow();
213
226
 
214
- expect(group.members).toContainEqual({
215
- id: "everyone",
216
- role: "writer",
217
- account: undefined,
218
- ref: undefined,
219
- });
227
+ expect(group.getRoleOf("everyone")).toBe("writer");
220
228
 
221
229
  // @ts-expect-error - writeOnly is not a valid role for everyone
222
230
  expect(() => group.addMember("everyone", "writeOnly")).toThrow();
223
231
 
224
- expect(group.members).toContainEqual({
225
- id: "everyone",
226
- role: "writer",
227
- account: undefined,
228
- ref: undefined,
229
- });
232
+ expect(group.getRoleOf("everyone")).toBe("writer");
230
233
  });
231
234
 
232
235
  test("typescript should show an error when adding a member with a non-account role", async () => {
233
236
  const account = await createJazzTestAccount({});
237
+ await account.waitForAllCoValuesSync();
234
238
 
235
239
  const group = Group.create();
236
240
 
237
241
  // @ts-expect-error - Even though readerInvite is a valid role for an account, we don't allow it to not create confusion when using the intellisense
238
242
  group.addMember(account, "readerInvite");
239
243
 
240
- expect(group.members).toContainEqual(
244
+ expect(group.members).not.toContainEqual(
241
245
  expect.objectContaining({
242
246
  id: account.id,
243
247
  role: "readerInvite",
244
248
  }),
245
249
  );
250
+
251
+ expect(group.getRoleOf(account.id)).toBe("readerInvite");
252
+ });
253
+ });
254
+
255
+ describe("Group.getRoleOf", () => {
256
+ beforeEach(async () => {
257
+ await createJazzTestAccount({ isCurrentActiveAccount: true });
258
+ });
259
+
260
+ test("returns correct role for admin", async () => {
261
+ const group = Group.create();
262
+ const admin = await createJazzTestAccount({});
263
+ await admin.waitForAllCoValuesSync();
264
+ group.addMember(admin, "admin");
265
+ expect(group.getRoleOf(admin.id)).toBe("admin");
266
+ expect(group.getRoleOf("me")).toBe("admin");
267
+ });
268
+
269
+ test("returns correct role for writer", async () => {
270
+ const group = Group.create();
271
+ const writer = await createJazzTestAccount({});
272
+ await writer.waitForAllCoValuesSync();
273
+ group.addMember(writer, "writer");
274
+ expect(group.getRoleOf(writer.id)).toBe("writer");
275
+ });
276
+
277
+ test("returns correct role for reader", async () => {
278
+ const group = Group.create();
279
+ const reader = await createJazzTestAccount({});
280
+ await reader.waitForAllCoValuesSync();
281
+ group.addMember(reader, "reader");
282
+ expect(group.getRoleOf(reader.id)).toBe("reader");
283
+ });
284
+
285
+ test("returns correct role for writeOnly", async () => {
286
+ const group = Group.create();
287
+ const writeOnly = await createJazzTestAccount({});
288
+ await writeOnly.waitForAllCoValuesSync();
289
+ group.addMember(writeOnly, "writeOnly");
290
+ expect(group.getRoleOf(writeOnly.id)).toBe("writeOnly");
291
+ });
292
+
293
+ test("returns correct role for everyone", () => {
294
+ const group = Group.create();
295
+ group.addMember("everyone", "reader");
296
+ expect(group.getRoleOf("everyone")).toBe("reader");
297
+ });
298
+ });
299
+
300
+ describe("Group.getRoleOf with 'me' parameter", () => {
301
+ beforeEach(async () => {
302
+ await createJazzTestAccount({ isCurrentActiveAccount: true });
303
+ });
304
+
305
+ test("returns correct role for 'me' when current account is admin", () => {
306
+ const group = Group.create();
307
+ expect(group.getRoleOf("me")).toBe("admin");
308
+ });
309
+
310
+ test("returns correct role for 'me' when current account is writer", async () => {
311
+ const account = await createJazzTestAccount();
312
+ await account.waitForAllCoValuesSync();
313
+ const group = Group.create({ owner: account });
314
+
315
+ group.addMember(Account.getMe(), "writer");
316
+
317
+ expect(group.getRoleOf("me")).toBe("writer");
318
+ });
319
+
320
+ test("returns correct role for 'me' when current account is reader", async () => {
321
+ const account = await createJazzTestAccount();
322
+ await account.waitForAllCoValuesSync();
323
+ const group = Group.create({ owner: account });
324
+
325
+ group.addMember(Account.getMe(), "reader");
326
+
327
+ expect(group.getRoleOf("me")).toBe("reader");
328
+ });
329
+
330
+ test("returns undefined for 'me' when current account has no role", async () => {
331
+ const account = await createJazzTestAccount();
332
+ await account.waitForAllCoValuesSync();
333
+ const group = Group.create({ owner: account });
334
+
335
+ expect(group.getRoleOf("me")).toBeUndefined();
336
+ });
337
+ });
338
+
339
+ describe("Account permissions", () => {
340
+ beforeEach(async () => {
341
+ await createJazzTestAccount({ isCurrentActiveAccount: true });
342
+ });
343
+
344
+ test("getRoleOf returns admin only for self and me", async () => {
345
+ const account = await Account.create({
346
+ creationProps: { name: "Test Account" },
347
+ crypto: Crypto,
348
+ });
349
+
350
+ // Account should be admin of itself
351
+ expect(account.getRoleOf(account.id)).toBe("admin");
352
+
353
+ // The GlobalMe is not this account
354
+ expect(account.getRoleOf("me")).toBe(undefined);
355
+ expect(Account.getMe().getRoleOf("me")).toBe("admin");
356
+
357
+ // Other accounts should have no role
358
+ const otherAccount = await Account.create({
359
+ creationProps: { name: "Other Account" },
360
+ crypto: Crypto,
361
+ });
362
+ expect(account.getRoleOf(otherAccount.id)).toBeUndefined();
363
+
364
+ // Everyone should have no role
365
+ expect(account.getRoleOf("everyone")).toBeUndefined();
366
+ });
367
+
368
+ test("members array only contains self as admin", async () => {
369
+ const account = await Account.create({
370
+ creationProps: { name: "Test Account" },
371
+ crypto: Crypto,
372
+ });
373
+
374
+ expect(account.members).toEqual([
375
+ { id: account.id, role: "admin", account: account, ref: expect.any(Ref) },
376
+ ]);
377
+ });
378
+ });
379
+
380
+ describe("Account permissions", () => {
381
+ test("canRead permissions for different roles", async () => {
382
+ // Create test accounts
383
+ const admin = await Account.create({
384
+ creationProps: { name: "Admin" },
385
+ crypto: Crypto,
386
+ });
387
+
388
+ const group = Group.create({ owner: admin });
389
+ const testObject = CoMap.create({}, { owner: group });
390
+
391
+ const writer = await Account.createAs(admin, {
392
+ creationProps: { name: "Writer" },
393
+ });
394
+ const reader = await Account.createAs(admin, {
395
+ creationProps: { name: "Reader" },
396
+ });
397
+ const writeOnly = await Account.createAs(admin, {
398
+ creationProps: { name: "WriteOnly" },
399
+ });
400
+
401
+ // Set up roles
402
+ group.addMember(writer, "writer");
403
+ group.addMember(reader, "reader");
404
+ group.addMember(writeOnly, "writeOnly");
405
+
406
+ // Test canRead permissions
407
+ expect(admin.canRead(testObject)).toBe(true);
408
+ expect(writer.canRead(testObject)).toBe(true);
409
+ expect(reader.canRead(testObject)).toBe(true);
410
+ expect(writeOnly.canRead(testObject)).toBe(true);
411
+ });
412
+
413
+ test("canWrite permissions for different roles", async () => {
414
+ // Create test accounts
415
+ const admin = await Account.create({
416
+ creationProps: { name: "Admin" },
417
+ crypto: Crypto,
418
+ });
419
+
420
+ const group = Group.create({ owner: admin });
421
+ const testObject = CoMap.create({}, { owner: group });
422
+
423
+ const writer = await Account.createAs(admin, {
424
+ creationProps: { name: "Writer" },
425
+ });
426
+ const reader = await Account.createAs(admin, {
427
+ creationProps: { name: "Reader" },
428
+ });
429
+ const writeOnly = await Account.createAs(admin, {
430
+ creationProps: { name: "WriteOnly" },
431
+ });
432
+
433
+ // Set up roles
434
+ group.addMember(writer, "writer");
435
+ group.addMember(reader, "reader");
436
+ group.addMember(writeOnly, "writeOnly");
437
+
438
+ // Test canWrite permissions
439
+ expect(admin.canWrite(testObject)).toBe(true);
440
+ expect(writer.canWrite(testObject)).toBe(true);
441
+ expect(reader.canWrite(testObject)).toBe(false);
442
+ expect(writeOnly.canWrite(testObject)).toBe(true);
443
+ });
444
+
445
+ test("canAdmin permissions for different roles", async () => {
446
+ // Create test accounts
447
+ const admin = await Account.create({
448
+ creationProps: { name: "Admin" },
449
+ crypto: Crypto,
450
+ });
451
+
452
+ const group = Group.create({ owner: admin });
453
+ const testObject = CoMap.create({}, { owner: group });
454
+
455
+ const writer = await Account.createAs(admin, {
456
+ creationProps: { name: "Writer" },
457
+ });
458
+ const reader = await Account.createAs(admin, {
459
+ creationProps: { name: "Reader" },
460
+ });
461
+ const writeOnly = await Account.createAs(admin, {
462
+ creationProps: { name: "WriteOnly" },
463
+ });
464
+
465
+ // Set up roles
466
+ group.addMember(writer, "writer");
467
+ group.addMember(reader, "reader");
468
+ group.addMember(writeOnly, "writeOnly");
469
+
470
+ // Test canAdmin permissions
471
+ expect(admin.canAdmin(testObject)).toBe(true);
472
+ expect(writer.canAdmin(testObject)).toBe(false);
473
+ expect(reader.canAdmin(testObject)).toBe(false);
474
+ expect(writeOnly.canAdmin(testObject)).toBe(false);
475
+ });
476
+
477
+ test("permissions for non-members", async () => {
478
+ const admin = await Account.create({
479
+ creationProps: { name: "Admin" },
480
+ crypto: Crypto,
481
+ });
482
+
483
+ const group = Group.create({ owner: admin });
484
+ const testObject = CoMap.create({}, { owner: group });
485
+
486
+ const nonMember = await Account.createAs(admin, {
487
+ creationProps: { name: "NonMember" },
488
+ });
489
+
490
+ // Test permissions for non-member
491
+ expect(nonMember.canRead(testObject)).toBe(false);
492
+ expect(nonMember.canWrite(testObject)).toBe(false);
493
+ expect(nonMember.canAdmin(testObject)).toBe(false);
494
+ });
495
+ });
496
+
497
+ describe("Group.members", () => {
498
+ test("should return the members of the group", async () => {
499
+ const childGroup = Group.create();
500
+
501
+ const bob = await createJazzTestAccount({});
502
+ await bob.waitForAllCoValuesSync();
503
+
504
+ childGroup.addMember(bob, "reader");
505
+ expect(childGroup.getRoleOf(bob.id)).toBe("reader");
506
+
507
+ expect(childGroup.members).toEqual([
508
+ expect.objectContaining({
509
+ account: expect.objectContaining({
510
+ id: Account.getMe().id,
511
+ }),
512
+ role: "admin",
513
+ }),
514
+ expect.objectContaining({
515
+ account: expect.objectContaining({
516
+ id: bob.id,
517
+ }),
518
+ role: "reader",
519
+ }),
520
+ ]);
521
+ });
522
+
523
+ test("should return the members of the parent group", async () => {
524
+ const childGroup = Group.create();
525
+ const parentGroup = Group.create();
526
+
527
+ const bob = await createJazzTestAccount({});
528
+ await bob.waitForAllCoValuesSync();
529
+
530
+ parentGroup.addMember(bob, "writer");
531
+ childGroup.extend(parentGroup, "reader");
532
+
533
+ expect(childGroup.getRoleOf(bob.id)).toBe("reader");
534
+
535
+ expect(childGroup.members).toEqual([
536
+ expect.objectContaining({
537
+ account: expect.objectContaining({
538
+ id: Account.getMe().id,
539
+ }),
540
+ role: "admin",
541
+ }),
542
+ expect.objectContaining({
543
+ account: expect.objectContaining({
544
+ id: bob.id,
545
+ }),
546
+ role: "reader",
547
+ }),
548
+ ]);
549
+ });
550
+
551
+ test("should not return everyone", async () => {
552
+ const childGroup = Group.create();
553
+
554
+ childGroup.addMember("everyone", "reader");
555
+ expect(childGroup.getRoleOf("everyone")).toBe("reader");
556
+
557
+ expect(childGroup.members).toEqual([
558
+ expect.objectContaining({
559
+ account: expect.objectContaining({
560
+ id: Account.getMe().id,
561
+ }),
562
+ role: "admin",
563
+ }),
564
+ ]);
565
+ });
566
+
567
+ test("should not return revoked members", async () => {
568
+ const childGroup = Group.create();
569
+
570
+ const bob = await createJazzTestAccount({});
571
+ await bob.waitForAllCoValuesSync();
572
+
573
+ childGroup.addMember(bob, "reader");
574
+ await childGroup.removeMember(bob);
575
+
576
+ expect(childGroup.getRoleOf(bob.id)).toBeUndefined();
577
+
578
+ expect(childGroup.members).toEqual([
579
+ expect.objectContaining({
580
+ account: expect.objectContaining({
581
+ id: Account.getMe().id,
582
+ }),
583
+ role: "admin",
584
+ }),
585
+ ]);
246
586
  });
247
587
  });
@@ -1,7 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
+ import { Account } from "../coValues/account";
2
3
  import { CoMap } from "../coValues/coMap";
3
4
  import { Group } from "../coValues/group";
4
5
  import { Inbox, InboxSender } from "../coValues/inbox";
6
+ import { Profile } from "../exports";
5
7
  import { co } from "../internal";
6
8
  import { setupTwoNodes, waitFor } from "./utils";
7
9
 
@@ -10,6 +12,28 @@ class Message extends CoMap {
10
12
  }
11
13
 
12
14
  describe("Inbox", () => {
15
+ describe("Private profile", () => {
16
+ it("Should throw if the inbox owner profile is private", async () => {
17
+ class WorkerAccount extends Account {
18
+ migrate() {
19
+ this.profile = Profile.create(
20
+ { name: "Worker" },
21
+ Group.create({ owner: this }),
22
+ );
23
+ }
24
+ }
25
+
26
+ const { clientAccount: sender, serverAccount: receiver } =
27
+ await setupTwoNodes({
28
+ ServerAccountSchema: WorkerAccount,
29
+ });
30
+
31
+ await expect(() => InboxSender.load(receiver.id, sender)).rejects.toThrow(
32
+ "Insufficient permissions to access the inbox, make sure its user profile is publicly readable.",
33
+ );
34
+ });
35
+ });
36
+
13
37
  it("should create inbox and allow message exchange between accounts", async () => {
14
38
  const { clientAccount: sender, serverAccount: receiver } =
15
39
  await setupTwoNodes();