cojson 0.18.0 → 0.18.2

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 (77) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/dist/coValueCore/branching.d.ts +36 -0
  4. package/dist/coValueCore/branching.d.ts.map +1 -0
  5. package/dist/coValueCore/branching.js +122 -0
  6. package/dist/coValueCore/branching.js.map +1 -0
  7. package/dist/coValueCore/coValueCore.d.ts +71 -5
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +162 -53
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts +3 -0
  12. package/dist/coValueCore/decodeTransactionChangesAndMeta.d.ts.map +1 -0
  13. package/dist/coValueCore/decodeTransactionChangesAndMeta.js +59 -0
  14. package/dist/coValueCore/decodeTransactionChangesAndMeta.js.map +1 -0
  15. package/dist/coValueCore/utils.d.ts.map +1 -1
  16. package/dist/coValueCore/utils.js +3 -0
  17. package/dist/coValueCore/utils.js.map +1 -1
  18. package/dist/coValueCore/verifiedState.d.ts.map +1 -1
  19. package/dist/coValues/coList.d.ts +3 -3
  20. package/dist/coValues/coList.d.ts.map +1 -1
  21. package/dist/coValues/coList.js +4 -7
  22. package/dist/coValues/coList.js.map +1 -1
  23. package/dist/coValues/coMap.d.ts +3 -3
  24. package/dist/coValues/coMap.d.ts.map +1 -1
  25. package/dist/coValues/coMap.js +6 -6
  26. package/dist/coValues/coMap.js.map +1 -1
  27. package/dist/coValues/coStream.d.ts +3 -3
  28. package/dist/coValues/coStream.d.ts.map +1 -1
  29. package/dist/coValues/coStream.js +4 -4
  30. package/dist/coValues/coStream.js.map +1 -1
  31. package/dist/ids.d.ts.map +1 -1
  32. package/dist/ids.js.map +1 -1
  33. package/dist/jsonStringify.d.ts +1 -0
  34. package/dist/jsonStringify.d.ts.map +1 -1
  35. package/dist/jsonStringify.js +8 -0
  36. package/dist/jsonStringify.js.map +1 -1
  37. package/dist/localNode.d.ts +6 -0
  38. package/dist/localNode.d.ts.map +1 -1
  39. package/dist/localNode.js +27 -0
  40. package/dist/localNode.js.map +1 -1
  41. package/dist/permissions.d.ts +2 -7
  42. package/dist/permissions.d.ts.map +1 -1
  43. package/dist/permissions.js +75 -71
  44. package/dist/permissions.js.map +1 -1
  45. package/dist/tests/branching.test.d.ts +2 -0
  46. package/dist/tests/branching.test.d.ts.map +1 -0
  47. package/dist/tests/branching.test.js +287 -0
  48. package/dist/tests/branching.test.js.map +1 -0
  49. package/dist/tests/coValueCore.test.js +2 -3
  50. package/dist/tests/coValueCore.test.js.map +1 -1
  51. package/dist/tests/group.removeMember.test.js +63 -116
  52. package/dist/tests/group.removeMember.test.js.map +1 -1
  53. package/dist/tests/sync.load.test.js +36 -0
  54. package/dist/tests/sync.load.test.js.map +1 -1
  55. package/dist/tests/sync.storage.test.js +39 -3
  56. package/dist/tests/sync.storage.test.js.map +1 -1
  57. package/dist/tests/sync.upload.test.js +39 -3
  58. package/dist/tests/sync.upload.test.js.map +1 -1
  59. package/package.json +2 -2
  60. package/src/coValueCore/branching.ts +198 -0
  61. package/src/coValueCore/coValueCore.ts +255 -72
  62. package/src/coValueCore/decodeTransactionChangesAndMeta.ts +81 -0
  63. package/src/coValueCore/utils.ts +4 -0
  64. package/src/coValueCore/verifiedState.ts +1 -1
  65. package/src/coValues/coList.ts +8 -10
  66. package/src/coValues/coMap.ts +8 -11
  67. package/src/coValues/coStream.ts +7 -8
  68. package/src/ids.ts +4 -1
  69. package/src/jsonStringify.ts +8 -0
  70. package/src/localNode.ts +40 -0
  71. package/src/permissions.ts +83 -90
  72. package/src/tests/branching.test.ts +425 -0
  73. package/src/tests/coValueCore.test.ts +2 -3
  74. package/src/tests/group.removeMember.test.ts +116 -214
  75. package/src/tests/sync.load.test.ts +48 -0
  76. package/src/tests/sync.storage.test.ts +54 -3
  77. package/src/tests/sync.upload.test.ts +53 -3
@@ -0,0 +1,425 @@
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import {
3
+ createTestNode,
4
+ setupTestNode,
5
+ loadCoValueOrFail,
6
+ } from "./testUtils.js";
7
+ import { expectMap } from "../coValue.js";
8
+
9
+ let jazzCloud: ReturnType<typeof setupTestNode>;
10
+
11
+ beforeEach(async () => {
12
+ jazzCloud = setupTestNode({ isSyncServer: true });
13
+ });
14
+
15
+ describe("Branching Logic", () => {
16
+ describe("Branch Operations with Transactions", () => {
17
+ test("should maintain separate transaction histories between original and branch", async () => {
18
+ const node = createTestNode();
19
+ const group = node.createGroup();
20
+ const originalMap = group.createMap();
21
+ const branchName = "feature-branch";
22
+
23
+ // Add initial transactions to original map
24
+ originalMap.set("originalKey1", "value1", "trusting");
25
+ originalMap.set("originalKey2", "value2", "trusting");
26
+
27
+ const branch = expectMap(
28
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
29
+ );
30
+
31
+ // Add new transactions to branch
32
+ branch.set("branchKey1", "branchValue1", "trusting");
33
+ branch.set("branchKey2", "branchValue2", "trusting");
34
+
35
+ // Verify original map doesn't have branch transactions
36
+ expect(originalMap.get("branchKey1")).toBe(undefined);
37
+ expect(originalMap.get("branchKey2")).toBe(undefined);
38
+
39
+ // Verify branch has both original and new transactions
40
+ expect(branch.get("branchKey2")).toBe("branchValue2");
41
+ expect(branch.get("branchKey1")).toBe("branchValue1");
42
+ expect(branch.get("originalKey1")).toBe("value1");
43
+ expect(branch.get("originalKey2")).toBe("value2");
44
+ });
45
+ });
46
+
47
+ describe("Branch Merging", () => {
48
+ test("should merge branch transactions back to source map", () => {
49
+ const node = createTestNode();
50
+ const group = node.createGroup();
51
+ const originalMap = group.createMap();
52
+ const branchName = "feature-branch";
53
+
54
+ // Add initial transactions to original map
55
+ originalMap.set("key1", "value1", "trusting");
56
+ originalMap.set("key2", "value2", "trusting");
57
+
58
+ // Create branch from original map
59
+ const branch = expectMap(
60
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
61
+ );
62
+
63
+ // Add new transaction to branch
64
+ branch.set("key1", "branchValue1", "trusting");
65
+
66
+ // Merge branch back to source
67
+ const result = expectMap(branch.core.mergeBranch().getCurrentContent());
68
+
69
+ // Verify source now contains branch transactions
70
+ expect(result.get("key1")).toBe("branchValue1");
71
+ expect(result.get("key2")).toBe("value2");
72
+ });
73
+
74
+ test("should not create duplicate merge commits when merging already merged branch", () => {
75
+ const node = createTestNode();
76
+ const group = node.createGroup();
77
+ const originalMap = group.createMap();
78
+ const branchName = "feature-branch";
79
+
80
+ // Add initial transactions to original map
81
+ originalMap.set("key1", "value1", "trusting");
82
+ originalMap.set("key2", "value2", "trusting");
83
+
84
+ // Create branch from original map
85
+ const branch = expectMap(
86
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
87
+ );
88
+
89
+ // Add new transaction to branch
90
+ branch.set("key1", "branchValue1", "trusting");
91
+
92
+ // Merge branch twice - second merge should not create new commit
93
+ expectMap(branch.core.mergeBranch().getCurrentContent());
94
+ const result = expectMap(branch.core.mergeBranch().getCurrentContent());
95
+
96
+ // Verify only one merge commit was created
97
+ expect(result.core.mergeCommits.length).toBe(1);
98
+
99
+ // Verify source contains branch transactions
100
+ expect(result.get("key1")).toBe("branchValue1");
101
+ expect(result.get("key2")).toBe("value2");
102
+ });
103
+
104
+ test("should not create merge commit when merging empty branch", () => {
105
+ const node = createTestNode();
106
+ const group = node.createGroup();
107
+ const originalMap = group.createMap();
108
+ const branchName = "feature-branch";
109
+
110
+ // Add initial transactions to original map
111
+ originalMap.set("key1", "value1", "trusting");
112
+ originalMap.set("key2", "value2", "trusting");
113
+
114
+ // Create branch from original map (no changes made)
115
+ const branch = expectMap(
116
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
117
+ );
118
+
119
+ // Merge empty branch
120
+ const result = expectMap(branch.core.mergeBranch().getCurrentContent());
121
+
122
+ // Verify no merge commit was created
123
+ expect(result.core.mergeCommits.length).toBe(0);
124
+ });
125
+
126
+ test("should merge only new changes from branch after previous merge", () => {
127
+ const node = createTestNode();
128
+ const group = node.createGroup();
129
+ const originalMap = group.createMap();
130
+ const branchName = "feature-branch";
131
+
132
+ // Add initial transactions to original map
133
+ originalMap.set("key1", "value1", "trusting");
134
+ originalMap.set("key2", "value2", "trusting");
135
+
136
+ const branch = expectMap(
137
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
138
+ );
139
+
140
+ // Add first change to branch
141
+ branch.set("key1", "branchValue1", "trusting");
142
+
143
+ // Merge first change
144
+ branch.core.mergeBranch();
145
+
146
+ // Verify first change was merged
147
+ expect(originalMap.get("key1")).toBe("branchValue1");
148
+ expect(originalMap.get("key2")).toBe("value2");
149
+
150
+ // Add second change to branch
151
+ branch.set("key2", "branchValue2", "trusting");
152
+
153
+ // Merge second change
154
+ branch.core.mergeBranch();
155
+
156
+ // Verify two merge commits exist
157
+ expect(originalMap.core.mergeCommits.length).toBe(2);
158
+
159
+ // Verify both changes are now in original map
160
+ expect(originalMap.get("key1")).toBe("branchValue1");
161
+ expect(originalMap.get("key2")).toBe("branchValue2");
162
+ });
163
+ });
164
+
165
+ describe("Branch Loading and Checkout", () => {
166
+ test("should create new branch when checking out non-existent branch", async () => {
167
+ const client = setupTestNode({
168
+ connected: true,
169
+ });
170
+ const group = jazzCloud.node.createGroup();
171
+ group.addMember("everyone", "writer");
172
+
173
+ const originalMap = group.createMap();
174
+ const branchName = "feature-branch";
175
+
176
+ // Add initial data to original map
177
+ originalMap.set("key1", "value1");
178
+ originalMap.set("key2", "value2");
179
+
180
+ // Checkout non-existent branch - should create new one
181
+ const branch = await client.node.checkoutBranch(
182
+ originalMap.id,
183
+ branchName,
184
+ );
185
+
186
+ expect(branch).not.toBe("unavailable");
187
+
188
+ if (branch !== "unavailable") {
189
+ // Verify branch inherits original data
190
+ expect(branch.get("key1")).toBe("value1");
191
+ expect(branch.get("key2")).toBe("value2");
192
+
193
+ // Add new data to branch
194
+ branch.set("branchKey", "branchValue");
195
+
196
+ await branch.core.waitForSync();
197
+
198
+ // Verify original map doesn't have branch data
199
+ expect(originalMap.get("branchKey")).toBe(undefined);
200
+ expect(branch.get("branchKey")).toBe("branchValue");
201
+ }
202
+ });
203
+
204
+ test("should load existing branch when checking out created branch", async () => {
205
+ const client = setupTestNode({
206
+ connected: true,
207
+ });
208
+ const group = jazzCloud.node.createGroup();
209
+ group.addMember("everyone", "writer");
210
+
211
+ const originalMap = group.createMap();
212
+ const branchName = "feature-branch";
213
+
214
+ // Add initial data to original map
215
+ originalMap.set("key1", "value1");
216
+ originalMap.set("key2", "value2");
217
+
218
+ // Create branch with some data
219
+ const originalBranch = expectMap(
220
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
221
+ );
222
+
223
+ originalBranch.set("branchKey", "branchValue");
224
+
225
+ // Checkout existing branch - should return created branch
226
+ const branch = await client.node.checkoutBranch(
227
+ originalMap.id,
228
+ branchName,
229
+ );
230
+
231
+ expect(branch).not.toBe("unavailable");
232
+
233
+ if (branch !== "unavailable") {
234
+ // Verify branch contains both original and new data
235
+ expect(branch.get("key1")).toBe("value1");
236
+ expect(branch.get("key2")).toBe("value2");
237
+ expect(branch.get("branchKey")).toBe("branchValue");
238
+ }
239
+ });
240
+
241
+ test("should successfully load existing branch via node.load", async () => {
242
+ const client = setupTestNode({
243
+ connected: true,
244
+ });
245
+ const group = jazzCloud.node.createGroup();
246
+ group.addMember("everyone", "writer");
247
+
248
+ const originalMap = group.createMap();
249
+ const branchName = "feature-branch";
250
+
251
+ // Add initial data to original map
252
+ originalMap.set("key1", "value1");
253
+ originalMap.set("key2", "value2");
254
+
255
+ // Create branch with some data
256
+ const originalBranch = expectMap(
257
+ originalMap.core.createBranch(branchName, group.id).getCurrentContent(),
258
+ );
259
+
260
+ originalBranch.set("branchKey", "branchValue");
261
+
262
+ // Load existing branch via node.load
263
+ const branch = await client.node.load(originalBranch.id);
264
+
265
+ expect(branch).not.toBe("unavailable");
266
+
267
+ if (branch !== "unavailable") {
268
+ // Verify branch contains all expected data
269
+ expect(branch.get("key1")).toBe("value1");
270
+ expect(branch.get("key2")).toBe("value2");
271
+ expect(branch.get("branchKey")).toBe("branchValue");
272
+ }
273
+ });
274
+
275
+ test("should create branch with different group owner when specified", async () => {
276
+ const client = setupTestNode({
277
+ connected: true,
278
+ });
279
+ const group = jazzCloud.node.createGroup();
280
+ group.addMember("everyone", "writer");
281
+
282
+ const originalMap = group.createMap();
283
+ const branchName = "feature-branch";
284
+
285
+ // Add initial data to original map
286
+ originalMap.set("key1", "value1");
287
+ originalMap.set("key2", "value2");
288
+
289
+ // Create different group to own the branch
290
+ const branchGroup = client.node.createGroup();
291
+
292
+ // Checkout branch with different group as owner
293
+ const branch = await client.node.checkoutBranch(
294
+ originalMap.id,
295
+ branchName,
296
+ branchGroup.id,
297
+ );
298
+
299
+ expect(branch).not.toBe("unavailable");
300
+
301
+ if (branch !== "unavailable") {
302
+ // Verify branch inherits original data
303
+ expect(branch.get("key1")).toBe("value1");
304
+ expect(branch.get("key2")).toBe("value2");
305
+
306
+ // Add new data to branch
307
+ branch.set("branchKey", "branchValue");
308
+
309
+ await branch.core.waitForSync();
310
+
311
+ // Verify original map doesn't have branch data
312
+ expect(originalMap.get("branchKey")).toBe(undefined);
313
+ expect(branch.get("branchKey")).toBe("branchValue");
314
+ }
315
+
316
+ // Verify that the sync server can't read the branch content
317
+ const branchOnTheServer = await jazzCloud.node.checkoutBranch(
318
+ originalMap.id,
319
+ branchName,
320
+ branchGroup.id,
321
+ );
322
+
323
+ expect(branchOnTheServer).not.toBe("unavailable");
324
+
325
+ if (branchOnTheServer !== "unavailable") {
326
+ expect(branchOnTheServer.get("branchKey")).toBe(undefined);
327
+ expect(branchOnTheServer.get("key1")).toBe(undefined);
328
+ expect(branchOnTheServer.get("key2")).toBe(undefined);
329
+ }
330
+ });
331
+
332
+ test("should return unavailable when trying to checkout branch from group", async () => {
333
+ const client = setupTestNode({
334
+ connected: true,
335
+ });
336
+ const group = jazzCloud.node.createGroup();
337
+ group.addMember("everyone", "writer");
338
+
339
+ // Try to checkout branch from group (groups don't support branching)
340
+ const branch = await client.node.checkoutBranch(
341
+ group.id,
342
+ "feature-branch",
343
+ );
344
+
345
+ // Should return unavailable since groups don't support branching
346
+ expect(branch).toBe("unavailable");
347
+ });
348
+
349
+ test("should return unavailable when source value is unreachable", async () => {
350
+ // Create client without sync server connection
351
+ const client = setupTestNode();
352
+
353
+ const group = jazzCloud.node.createGroup();
354
+ group.addMember("everyone", "writer");
355
+
356
+ // Create map on sync server (unreachable from client)
357
+ const originalMap = group.createMap();
358
+ originalMap.set("key1", "value1");
359
+
360
+ // Try to checkout branch on unreachable map
361
+ const branch = await client.node.checkoutBranch(
362
+ originalMap.id,
363
+ "feature-branch",
364
+ );
365
+
366
+ // Should return unavailable since checkout failed
367
+ expect(branch).toBe("unavailable");
368
+ });
369
+ });
370
+
371
+ describe("Branch Conflict Resolution", () => {
372
+ test("should successfully handle concurrent branch creation on different nodes", async () => {
373
+ const bob = setupTestNode();
374
+ const { peer: bobPeer } = bob.connectToSyncServer();
375
+ const alice = setupTestNode({
376
+ connected: true,
377
+ });
378
+
379
+ const client = setupTestNode();
380
+ const group = jazzCloud.node.createGroup();
381
+ group.addMember("everyone", "writer");
382
+
383
+ // Create map without any branches
384
+ const originalMap = group.createMap();
385
+
386
+ const originalMapOnBob = await loadCoValueOrFail(
387
+ bob.node,
388
+ originalMap.id,
389
+ );
390
+
391
+ // Disconnect bob from sync server to create isolation
392
+ bobPeer.outgoing.close();
393
+
394
+ // Create branches on different nodes
395
+ const aliceBranch = await alice.node.checkoutBranch(
396
+ originalMap.id,
397
+ "feature-branch",
398
+ );
399
+ const bobBranch = expectMap(
400
+ originalMapOnBob.core
401
+ .createBranch("feature-branch", group.id)
402
+ .getCurrentContent(),
403
+ );
404
+
405
+ if (aliceBranch === "unavailable") {
406
+ throw new Error("Alice branch is unavailable");
407
+ }
408
+
409
+ // Add different data to each branch
410
+ bobBranch.set("bob", true);
411
+ aliceBranch.set("alice", true);
412
+
413
+ // Reconnect bob to sync server
414
+ bob.connectToSyncServer();
415
+
416
+ // Wait for sync to complete
417
+ await bobBranch.core.waitForSync();
418
+ await aliceBranch.core.waitForSync();
419
+
420
+ // Verify both branches now contain data from the other
421
+ expect(bobBranch.get("alice")).toBe(true);
422
+ expect(aliceBranch.get("bob")).toBe(true);
423
+ });
424
+ });
425
+ });
@@ -277,9 +277,8 @@ test("creates a transaction with trusting meta information", async () => {
277
277
  meta: true,
278
278
  });
279
279
 
280
- const validTransactions = determineValidTransactions(map.core);
281
-
282
- expect(validTransactions[0]?.tx.meta).toBe(`{"meta":true}`);
280
+ expect(map.core.verifiedTransactions[0]?.tx.meta).toBe(`{"meta":true}`);
281
+ expect(map.core.verifiedTransactions[0]?.meta).toEqual({ meta: true });
283
282
  });
284
283
 
285
284
  test("creates a transaction with private meta information", async () => {