jazz-tools 0.10.15 → 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 (146) hide show
  1. package/.turbo/turbo-build.log +9 -5
  2. package/CHANGELOG.md +23 -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-5USJBXLW.js → chunk-RTRX7HIO.js} +153 -69
  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 +1 -1
  81. package/dist/tests/AuthSecretStorage.test.d.ts +2 -0
  82. package/dist/tests/AuthSecretStorage.test.d.ts.map +1 -0
  83. package/dist/tests/ContextManager.test.d.ts +2 -0
  84. package/dist/tests/ContextManager.test.d.ts.map +1 -0
  85. package/dist/tests/DemoAuth.test.d.ts +2 -0
  86. package/dist/tests/DemoAuth.test.d.ts.map +1 -0
  87. package/dist/tests/PassphraseAuth.test.d.ts +2 -0
  88. package/dist/tests/PassphraseAuth.test.d.ts.map +1 -0
  89. package/dist/tests/account.test.d.ts +2 -0
  90. package/dist/tests/account.test.d.ts.map +1 -0
  91. package/dist/tests/coFeed.test.d.ts +2 -0
  92. package/dist/tests/coFeed.test.d.ts.map +1 -0
  93. package/dist/tests/coList.test.d.ts +2 -0
  94. package/dist/tests/coList.test.d.ts.map +1 -0
  95. package/dist/tests/coMap.test.d.ts +2 -0
  96. package/dist/tests/coMap.test.d.ts.map +1 -0
  97. package/dist/tests/coPlainText.test.d.ts +2 -0
  98. package/dist/tests/coPlainText.test.d.ts.map +1 -0
  99. package/dist/tests/coRichText.test.d.ts +2 -0
  100. package/dist/tests/coRichText.test.d.ts.map +1 -0
  101. package/dist/tests/createContext.test.d.ts +2 -0
  102. package/dist/tests/createContext.test.d.ts.map +1 -0
  103. package/dist/tests/deepLoading.test.d.ts +2 -0
  104. package/dist/tests/deepLoading.test.d.ts.map +1 -0
  105. package/dist/tests/fixtures.d.ts +2 -0
  106. package/dist/tests/fixtures.d.ts.map +1 -0
  107. package/dist/tests/groupsAndAccounts.test.d.ts +2 -0
  108. package/dist/tests/groupsAndAccounts.test.d.ts.map +1 -0
  109. package/dist/tests/inbox.test.d.ts +2 -0
  110. package/dist/tests/inbox.test.d.ts.map +1 -0
  111. package/dist/tests/interfaces.test.d.ts +2 -0
  112. package/dist/tests/interfaces.test.d.ts.map +1 -0
  113. package/dist/tests/invites.test.d.ts +2 -0
  114. package/dist/tests/invites.test.d.ts.map +1 -0
  115. package/dist/tests/schema.test.d.ts +2 -0
  116. package/dist/tests/schema.test.d.ts.map +1 -0
  117. package/dist/tests/schemaUnion.test.d.ts +2 -0
  118. package/dist/tests/schemaUnion.test.d.ts.map +1 -0
  119. package/dist/tests/subscribe.test.d.ts +2 -0
  120. package/dist/tests/subscribe.test.d.ts.map +1 -0
  121. package/dist/tests/testing.test.d.ts +2 -0
  122. package/dist/tests/testing.test.d.ts.map +1 -0
  123. package/dist/tests/utils.d.ts +21 -0
  124. package/dist/tests/utils.d.ts.map +1 -0
  125. package/dist/types.d.ts +52 -0
  126. package/dist/types.d.ts.map +1 -0
  127. package/package.json +8 -7
  128. package/src/coValues/account.ts +69 -11
  129. package/src/coValues/coMap.ts +2 -2
  130. package/src/coValues/coRichText.ts +42 -17
  131. package/src/coValues/group.ts +76 -31
  132. package/src/coValues/inbox.ts +10 -0
  133. package/src/coValues/interfaces.ts +1 -1
  134. package/src/coValues/profile.ts +35 -2
  135. package/src/implementation/schema.ts +1 -3
  136. package/src/tests/AuthSecretStorage.test.ts +1 -2
  137. package/src/tests/PassphraseAuth.test.ts +3 -3
  138. package/src/tests/coMap.test.ts +20 -21
  139. package/src/tests/deepLoading.test.ts +8 -17
  140. package/src/tests/groupsAndAccounts.test.ts +429 -89
  141. package/src/tests/inbox.test.ts +24 -0
  142. package/src/tests/schema.test.ts +45 -5
  143. package/src/tests/utils.ts +7 -3
  144. package/src/types.ts +6 -0
  145. package/tsconfig.json +4 -1
  146. package/dist/chunk-5USJBXLW.js.map +0 -1
@@ -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();
@@ -140,6 +140,15 @@ describe("co.json TypeScript validation", () => {
140
140
  }>();
141
141
  });
142
142
 
143
+ it("should flag types with symbol keys as invalid", async () => {
144
+ type InvalidType = { [key: symbol]: string };
145
+
146
+ class InvalidFunctionMap extends CoMap {
147
+ // @ts-expect-error Should not be considered valid
148
+ data = co.json<InvalidType>();
149
+ }
150
+ });
151
+
143
152
  it("should apply the same validation to optional json", async () => {
144
153
  type ValidType = {
145
154
  value: string;
@@ -163,17 +172,48 @@ describe("co.json TypeScript validation", () => {
163
172
  }>();
164
173
  });
165
174
 
175
+ /* Special case from reported issue:
176
+ ** See: https://github.com/garden-co/jazz/issues/1496
177
+ */
178
+ it("should apply the same validation to optional json [JAZZ-1496]", async () => {
179
+ interface ValidInterface0 {
180
+ value: string;
181
+ }
182
+ interface ValidInterface1 {
183
+ value: string | undefined;
184
+ }
185
+ interface InterfaceWithOptionalTypes {
186
+ requiredValue: string;
187
+ value?: string;
188
+ }
189
+
190
+ class MapWithOptionalJSON extends CoMap {
191
+ data1 = co.optional.json<ValidInterface0>();
192
+ data2 = co.optional.json<ValidInterface1>();
193
+ data3 = co.optional.json<InterfaceWithOptionalTypes>();
194
+ }
195
+
196
+ expectTypeOf(MapWithOptionalJSON.create<MapWithOptionalJSON>)
197
+ .parameter(0)
198
+ .toEqualTypeOf<{
199
+ data1?: valueWithCoMarker<ValidInterface0> | null;
200
+ data2?: valueWithCoMarker<ValidInterface1> | null;
201
+ data3?: valueWithCoMarker<InterfaceWithOptionalTypes> | null;
202
+ }>();
203
+ });
204
+
166
205
  it("should not accept functions", async () => {
167
206
  class InvalidFunctionMap extends CoMap {
168
207
  // @ts-expect-error Should not be considered valid
169
208
  data = co.json<() => void>();
170
209
  }
210
+ });
171
211
 
172
- expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
173
- .parameter(0)
174
- .toEqualTypeOf<{
175
- data: valueWithCoMarker<() => void>;
176
- }>();
212
+ it("should not accept functions in nested properties", async () => {
213
+ class InvalidFunctionMap extends CoMap {
214
+ // @ts-expect-error Should not be considered valid
215
+ data = co.json<{ func: () => void }>();
216
+ }
177
217
  });
178
218
 
179
219
  it("should not accept RegExp", async () => {