cojson 0.8.39 → 0.8.44

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 (42) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/CHANGELOG.md +14 -0
  3. package/dist/native/coValueCore.js +22 -5
  4. package/dist/native/coValueCore.js.map +1 -1
  5. package/dist/native/coValues/coMap.js +98 -103
  6. package/dist/native/coValues/coMap.js.map +1 -1
  7. package/dist/native/coValues/coStream.js +17 -6
  8. package/dist/native/coValues/coStream.js.map +1 -1
  9. package/dist/native/coValues/group.js +127 -39
  10. package/dist/native/coValues/group.js.map +1 -1
  11. package/dist/native/exports.js +2 -0
  12. package/dist/native/exports.js.map +1 -1
  13. package/dist/native/localNode.js +5 -2
  14. package/dist/native/localNode.js.map +1 -1
  15. package/dist/native/permissions.js +77 -19
  16. package/dist/native/permissions.js.map +1 -1
  17. package/dist/web/coValueCore.js +22 -5
  18. package/dist/web/coValueCore.js.map +1 -1
  19. package/dist/web/coValues/coMap.js +98 -103
  20. package/dist/web/coValues/coMap.js.map +1 -1
  21. package/dist/web/coValues/coStream.js +17 -6
  22. package/dist/web/coValues/coStream.js.map +1 -1
  23. package/dist/web/coValues/group.js +127 -39
  24. package/dist/web/coValues/group.js.map +1 -1
  25. package/dist/web/exports.js +2 -0
  26. package/dist/web/exports.js.map +1 -1
  27. package/dist/web/localNode.js +5 -2
  28. package/dist/web/localNode.js.map +1 -1
  29. package/dist/web/permissions.js +77 -19
  30. package/dist/web/permissions.js.map +1 -1
  31. package/package.json +3 -5
  32. package/src/coValueCore.ts +37 -9
  33. package/src/coValues/coMap.ts +126 -127
  34. package/src/coValues/coStream.ts +27 -10
  35. package/src/coValues/group.ts +218 -50
  36. package/src/exports.ts +2 -0
  37. package/src/localNode.ts +5 -2
  38. package/src/permissions.ts +105 -24
  39. package/src/tests/coMap.test.ts +2 -2
  40. package/src/tests/group.test.ts +332 -38
  41. package/src/tests/permissions.test.ts +324 -0
  42. package/src/tests/testUtils.ts +18 -13
@@ -1,4 +1,4 @@
1
- import { expect, test } from "vitest";
1
+ import { describe, expect, test } from "vitest";
2
2
  import { RawCoList } from "../coValues/coList.js";
3
3
  import { RawCoMap } from "../coValues/coMap.js";
4
4
  import { RawCoStream } from "../coValues/coStream.js";
@@ -7,6 +7,7 @@ import { WasmCrypto } from "../crypto/WasmCrypto.js";
7
7
  import { LocalNode } from "../localNode.js";
8
8
  import {
9
9
  createThreeConnectedNodes,
10
+ createTwoConnectedNodes,
10
11
  loadCoValueOrFail,
11
12
  randomAnonymousAccountAndSessionID,
12
13
  } from "./testUtils.js";
@@ -59,33 +60,44 @@ test("Can create a FileStream in a group", () => {
59
60
  });
60
61
 
61
62
  test("Remove a member from a group where the admin role is inherited", async () => {
62
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode3Peer } =
63
- createThreeConnectedNodes("server", "server", "server");
63
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
64
+ "server",
65
+ "server",
66
+ "server",
67
+ );
64
68
 
65
- const group = node1.createGroup();
69
+ const group = node1.node.createGroup();
66
70
 
67
- group.addMember(node2.account, "admin");
68
- group.addMember(node3.account, "reader");
71
+ group.addMember(
72
+ await loadCoValueOrFail(node1.node, node2.accountID),
73
+ "admin",
74
+ );
75
+ group.addMember(
76
+ await loadCoValueOrFail(node1.node, node3.accountID),
77
+ "reader",
78
+ );
79
+
80
+ await group.core.waitForSync();
69
81
 
70
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
82
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
71
83
 
72
84
  // The account of node2 create a child group and extend the initial group
73
85
  // This way the node1 account should become "admin" of the child group
74
86
  // by inheriting the admin role from the initial group
75
- const childGroup = node2.createGroup();
87
+ const childGroup = node2.node.createGroup();
76
88
  childGroup.extend(groupOnNode2);
77
89
 
78
90
  const map = childGroup.createMap();
79
91
  map.set("test", "Available to everyone");
80
92
 
81
- const mapOnNode3 = await loadCoValueOrFail(node3, map.id);
93
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
82
94
 
83
95
  // Check that the sync between node2 and node3 worked
84
96
  expect(mapOnNode3.get("test")).toEqual("Available to everyone");
85
97
 
86
98
  // The node1 account removes the reader from the group
87
99
  // The reader should be automatically kicked out of the child group
88
- await group.removeMember(node3.account);
100
+ await group.removeMember(node3.node.account);
89
101
 
90
102
  await group.core.waitForSync();
91
103
 
@@ -97,26 +109,37 @@ test("Remove a member from a group where the admin role is inherited", async ()
97
109
  // Check that the value has not been updated on node3
98
110
  expect(mapOnNode3.get("test")).toEqual("Available to everyone");
99
111
 
100
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
112
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
101
113
 
102
114
  expect(mapOnNode1.get("test")).toEqual("Hidden to node3");
103
115
  });
104
116
 
105
117
  test("An admin should be able to rotate the readKey on child groups and keep access to new coValues", async () => {
106
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
107
- createThreeConnectedNodes("server", "server", "server");
118
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
119
+ "server",
120
+ "server",
121
+ "server",
122
+ );
108
123
 
109
- const group = node1.createGroup();
124
+ const group = node1.node.createGroup();
110
125
 
111
- group.addMember(node2.account, "admin");
112
- group.addMember(node3.account, "reader");
126
+ group.addMember(
127
+ await loadCoValueOrFail(node1.node, node2.accountID),
128
+ "admin",
129
+ );
130
+ group.addMember(
131
+ await loadCoValueOrFail(node1.node, node3.accountID),
132
+ "reader",
133
+ );
113
134
 
114
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
135
+ await group.core.waitForSync();
136
+
137
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
115
138
 
116
139
  // The account of node2 create a child group and extend the initial group
117
140
  // This way the node1 account should become "admin" of the child group
118
141
  // by inheriting the admin role from the initial group
119
- const childGroup = node2.createGroup();
142
+ const childGroup = node2.node.createGroup();
120
143
  childGroup.extend(groupOnNode2);
121
144
 
122
145
  await childGroup.core.waitForSync();
@@ -124,78 +147,349 @@ test("An admin should be able to rotate the readKey on child groups and keep acc
124
147
  // The node1 account removes the reader from the group
125
148
  // In this case we want to ensure that node1 is still able to read new coValues
126
149
  // Even if some childs are not available when the readKey is rotated
127
- await group.removeMember(node3.account);
150
+ await group.removeMember(node3.node.account);
128
151
  await group.core.waitForSync();
129
152
 
130
153
  const map = childGroup.createMap();
131
154
  map.set("test", "Available to node1");
132
155
 
133
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
156
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
134
157
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
135
158
  });
136
159
 
137
160
  test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group", async () => {
138
- const { node1, node2, node3, node1ToNode2Peer, node2ToNode1Peer } =
139
- createThreeConnectedNodes("server", "server", "server");
161
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
162
+ "server",
163
+ "server",
164
+ "server",
165
+ );
166
+
167
+ const group = node1.node.createGroup();
140
168
 
141
- const group = node1.createGroup();
169
+ group.addMember(
170
+ await loadCoValueOrFail(node1.node, node2.accountID),
171
+ "admin",
172
+ );
173
+ group.addMember(
174
+ await loadCoValueOrFail(node1.node, node3.accountID),
175
+ "reader",
176
+ );
142
177
 
143
- group.addMember(node2.account, "admin");
144
- group.addMember(node3.account, "reader");
178
+ await group.core.waitForSync();
145
179
 
146
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
180
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
147
181
 
148
182
  // The account of node2 create a child group and extend the initial group
149
183
  // This way the node1 account should become "admin" of the child group
150
184
  // by inheriting the admin role from the initial group
151
- const childGroup = node2.createGroup();
185
+ const childGroup = node2.node.createGroup();
152
186
  childGroup.extend(groupOnNode2);
153
187
 
154
188
  // The node1 account removes the reader from the group
155
189
  // In this case we want to ensure that node1 is still able to read new coValues
156
190
  // Even if some childs are not available when the readKey is rotated
157
- await group.removeMember(node3.account);
191
+ await group.removeMember(node3.node.account);
158
192
  await group.core.waitForSync();
159
193
 
160
194
  const map = childGroup.createMap();
161
195
  map.set("test", "Available to node1");
162
196
 
163
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
197
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
164
198
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
165
199
  });
166
200
 
167
201
  test("An admin should be able to rotate the readKey on child groups even if it was unavailable when kicking out a member from a parent group (grandChild)", async () => {
168
- const { node1, node2, node3, node1ToNode2Peer } = createThreeConnectedNodes(
202
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
169
203
  "server",
170
204
  "server",
171
205
  "server",
172
206
  );
173
207
 
174
- const group = node1.createGroup();
208
+ const group = node1.node.createGroup();
175
209
 
176
- group.addMember(node2.account, "admin");
177
- group.addMember(node3.account, "reader");
210
+ group.addMember(
211
+ await loadCoValueOrFail(node1.node, node2.accountID),
212
+ "admin",
213
+ );
214
+ group.addMember(
215
+ await loadCoValueOrFail(node1.node, node3.accountID),
216
+ "reader",
217
+ );
178
218
 
179
- const groupOnNode2 = await loadCoValueOrFail(node2, group.id);
219
+ await group.core.waitForSync();
220
+
221
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
180
222
 
181
223
  // The account of node2 create a child group and extend the initial group
182
224
  // This way the node1 account should become "admin" of the child group
183
225
  // by inheriting the admin role from the initial group
184
- const childGroup = node2.createGroup();
226
+ const childGroup = node2.node.createGroup();
185
227
  childGroup.extend(groupOnNode2);
186
- const grandChildGroup = node2.createGroup();
228
+ const grandChildGroup = node2.node.createGroup();
187
229
  grandChildGroup.extend(childGroup);
188
230
 
189
231
  // The node1 account removes the reader from the group
190
232
  // In this case we want to ensure that node1 is still able to read new coValues
191
233
  // Even if some childs are not available when the readKey is rotated
192
- await group.removeMember(node3.account);
234
+ await group.removeMember(node3.node.account);
193
235
  await group.core.waitForSync();
194
236
 
195
237
  const map = childGroup.createMap();
196
238
  map.set("test", "Available to node1");
197
239
 
198
- const mapOnNode1 = await loadCoValueOrFail(node1, map.id);
240
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
199
241
 
200
242
  expect(mapOnNode1.get("test")).toEqual("Available to node1");
201
243
  });
244
+
245
+ test("A user add after a key rotation should have access to the old transactions", async () => {
246
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
247
+ "server",
248
+ "server",
249
+ "server",
250
+ );
251
+
252
+ const group = node1.node.createGroup();
253
+
254
+ group.addMember(
255
+ await loadCoValueOrFail(node1.node, node2.accountID),
256
+ "writer",
257
+ );
258
+
259
+ await group.core.waitForSync();
260
+
261
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
262
+
263
+ const map = groupOnNode2.createMap();
264
+ map.set("test", "Written from node2");
265
+
266
+ await map.core.waitForSync();
267
+
268
+ await group.removeMember(node3.node.account);
269
+ group.addMember(
270
+ await loadCoValueOrFail(node1.node, node3.accountID),
271
+ "reader",
272
+ );
273
+
274
+ await group.core.waitForSync();
275
+
276
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
277
+ expect(mapOnNode3.get("test")).toEqual("Written from node2");
278
+ });
279
+
280
+ test("Invites should have access to the new keys", async () => {
281
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
282
+ "server",
283
+ "server",
284
+ "server",
285
+ );
286
+
287
+ const group = node1.node.createGroup();
288
+ group.addMember(
289
+ await loadCoValueOrFail(node1.node, node3.accountID),
290
+ "reader",
291
+ );
292
+
293
+ const invite = group.createInvite("admin");
294
+
295
+ await group.removeMember(node3.node.account);
296
+
297
+ const map = group.createMap();
298
+ map.set("test", "Written from node1");
299
+
300
+ await map.core.waitForSync();
301
+
302
+ await node2.node.acceptInvite(group.id, invite);
303
+
304
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
305
+ expect(mapOnNode2.get("test")).toEqual("Written from node1");
306
+ });
307
+
308
+ describe("writeOnly", () => {
309
+ test("Admins can invite writeOnly members", async () => {
310
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
311
+
312
+ const group = node1.node.createGroup();
313
+
314
+ const invite = group.createInvite("writeOnly");
315
+
316
+ await node2.node.acceptInvite(group.id, invite);
317
+
318
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
319
+ expect(groupOnNode2.myRole()).toEqual("writeOnly");
320
+ });
321
+
322
+ test("writeOnly roles are not inherited", async () => {
323
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
324
+
325
+ const group = node1.node.createGroup();
326
+ group.addMember(
327
+ await loadCoValueOrFail(node1.node, node2.accountID),
328
+ "writeOnly",
329
+ );
330
+
331
+ const childGroup = node1.node.createGroup();
332
+ childGroup.extend(group);
333
+ expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
334
+ });
335
+
336
+ test("writeOnly roles are not overridded by reader roles", async () => {
337
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
338
+
339
+ const group = node1.node.createGroup();
340
+ group.addMember(
341
+ await loadCoValueOrFail(node1.node, node2.accountID),
342
+ "reader",
343
+ );
344
+
345
+ const childGroup = node1.node.createGroup();
346
+ childGroup.extend(group);
347
+ childGroup.addMember(
348
+ await loadCoValueOrFail(node1.node, node2.accountID),
349
+ "writeOnly",
350
+ );
351
+
352
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writeOnly");
353
+ });
354
+
355
+ test("writeOnly roles are overridded by writer roles", async () => {
356
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
357
+
358
+ const group = node1.node.createGroup();
359
+ group.addMember(
360
+ await loadCoValueOrFail(node1.node, node2.accountID),
361
+ "writer",
362
+ );
363
+
364
+ const childGroup = node1.node.createGroup();
365
+ childGroup.extend(group);
366
+ childGroup.addMember(
367
+ await loadCoValueOrFail(node1.node, node2.accountID),
368
+ "writeOnly",
369
+ );
370
+
371
+ expect(childGroup.roleOf(node2.accountID)).toEqual("writer");
372
+ });
373
+
374
+ test("Edits by writeOnly members are visible to other members", async () => {
375
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
376
+ "server",
377
+ "server",
378
+ "server",
379
+ );
380
+
381
+ const group = node1.node.createGroup();
382
+
383
+ group.addMember(
384
+ await loadCoValueOrFail(node1.node, node2.accountID),
385
+ "writeOnly",
386
+ );
387
+ group.addMember(
388
+ await loadCoValueOrFail(node1.node, node3.accountID),
389
+ "reader",
390
+ );
391
+
392
+ await group.core.waitForSync();
393
+
394
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
395
+ const map = groupOnNode2.createMap();
396
+
397
+ map.set("test", "Written from a writeOnly member");
398
+ expect(map.get("test")).toEqual("Written from a writeOnly member");
399
+
400
+ await map.core.waitForSync();
401
+
402
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
403
+ expect(mapOnNode1.get("test")).toEqual("Written from a writeOnly member");
404
+
405
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
406
+ expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
407
+ });
408
+
409
+ test("Edits by other members are not visible to writeOnly members", async () => {
410
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
411
+
412
+ const group = node1.node.createGroup();
413
+
414
+ group.addMember(
415
+ await loadCoValueOrFail(node1.node, node2.accountID),
416
+ "writeOnly",
417
+ );
418
+ const map = group.createMap();
419
+ map.set("test", "Written from the admin");
420
+
421
+ await map.core.waitForSync();
422
+
423
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
424
+ expect(mapOnNode2.get("test")).toEqual(undefined);
425
+ });
426
+
427
+ test("Write only member keys are rotated when a member is kicked out", async () => {
428
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
429
+ "server",
430
+ "server",
431
+ "server",
432
+ );
433
+
434
+ const group = node1.node.createGroup();
435
+
436
+ group.addMember(
437
+ await loadCoValueOrFail(node1.node, node2.accountID),
438
+ "writeOnly",
439
+ );
440
+ group.addMember(
441
+ await loadCoValueOrFail(node1.node, node3.accountID),
442
+ "reader",
443
+ );
444
+
445
+ await group.core.waitForSync();
446
+
447
+ const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
448
+ const map = groupOnNode2.createMap();
449
+
450
+ map.set("test", "Written from a writeOnly member");
451
+
452
+ await map.core.waitForSync();
453
+
454
+ await group.removeMember(node3.node.account);
455
+
456
+ await group.core.waitForSync();
457
+
458
+ map.set("test", "Updated after key rotation");
459
+
460
+ await map.core.waitForSync();
461
+
462
+ const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
463
+ expect(mapOnNode1.get("test")).toEqual("Updated after key rotation");
464
+
465
+ const mapOnNode3 = await loadCoValueOrFail(node3.node, map.id);
466
+ expect(mapOnNode3.get("test")).toEqual("Written from a writeOnly member");
467
+ });
468
+
469
+ test("inherited writer roles should work correctly", async () => {
470
+ const { node1, node2 } = await createTwoConnectedNodes("server", "server");
471
+
472
+ const group = node1.node.createGroup();
473
+ group.addMember(
474
+ await loadCoValueOrFail(node1.node, node2.accountID),
475
+ "writer",
476
+ );
477
+
478
+ const childGroup = node1.node.createGroup();
479
+ childGroup.extend(group);
480
+ childGroup.addMember(
481
+ await loadCoValueOrFail(node1.node, node2.accountID),
482
+ "writeOnly",
483
+ );
484
+
485
+ const map = childGroup.createMap();
486
+ map.set("test", "Written from the admin");
487
+
488
+ await map.core.waitForSync();
489
+
490
+ const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
491
+
492
+ // The writer role should be able to see the edits from the admin
493
+ expect(mapOnNode2.get("test")).toEqual("Written from the admin");
494
+ });
495
+ });