cojson 0.18.4 → 0.18.6

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 (49) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/coValueCore/branching.d.ts +5 -5
  4. package/dist/coValueCore/branching.d.ts.map +1 -1
  5. package/dist/coValueCore/branching.js +72 -4
  6. package/dist/coValueCore/branching.js.map +1 -1
  7. package/dist/coValueCore/coValueCore.d.ts +4 -0
  8. package/dist/coValueCore/coValueCore.d.ts.map +1 -1
  9. package/dist/coValueCore/coValueCore.js +37 -16
  10. package/dist/coValueCore/coValueCore.js.map +1 -1
  11. package/dist/coValues/coList.d.ts +8 -2
  12. package/dist/coValues/coList.d.ts.map +1 -1
  13. package/dist/coValues/coList.js +86 -58
  14. package/dist/coValues/coList.js.map +1 -1
  15. package/dist/exports.d.ts +2 -2
  16. package/dist/exports.d.ts.map +1 -1
  17. package/dist/exports.js +1 -1
  18. package/dist/exports.js.map +1 -1
  19. package/dist/ids.d.ts +1 -0
  20. package/dist/ids.d.ts.map +1 -1
  21. package/dist/ids.js.map +1 -1
  22. package/dist/storage/storageAsync.d.ts.map +1 -1
  23. package/dist/storage/storageAsync.js +7 -4
  24. package/dist/storage/storageAsync.js.map +1 -1
  25. package/dist/storage/storageSync.d.ts.map +1 -1
  26. package/dist/storage/storageSync.js +7 -4
  27. package/dist/storage/storageSync.js.map +1 -1
  28. package/dist/tests/branching.test.js +65 -1
  29. package/dist/tests/branching.test.js.map +1 -1
  30. package/dist/tests/coList.test.js +367 -1
  31. package/dist/tests/coList.test.js.map +1 -1
  32. package/dist/tests/messagesTestUtils.d.ts +1 -1
  33. package/dist/tests/sync.load.test.js +2 -2
  34. package/dist/tests/sync.load.test.js.map +1 -1
  35. package/dist/tests/sync.storageAsync.test.js +6 -10
  36. package/dist/tests/sync.storageAsync.test.js.map +1 -1
  37. package/dist/tests/testUtils.d.ts +2 -2
  38. package/package.json +2 -2
  39. package/src/coValueCore/branching.ts +109 -9
  40. package/src/coValueCore/coValueCore.ts +45 -17
  41. package/src/coValues/coList.ts +118 -78
  42. package/src/exports.ts +6 -1
  43. package/src/ids.ts +1 -0
  44. package/src/storage/storageAsync.ts +8 -4
  45. package/src/storage/storageSync.ts +8 -4
  46. package/src/tests/branching.test.ts +103 -1
  47. package/src/tests/coList.test.ts +529 -1
  48. package/src/tests/sync.load.test.ts +2 -2
  49. package/src/tests/sync.storageAsync.test.ts +9 -12
@@ -1,11 +1,7 @@
1
1
  import { CoID, RawCoValue } from "../coValue.js";
2
- import {
3
- AvailableCoValueCore,
4
- CoValueCore,
5
- } from "../coValueCore/coValueCore.js";
2
+ import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
6
3
  import { AgentID, SessionID, TransactionID } from "../ids.js";
7
4
  import { JsonObject, JsonValue } from "../jsonValue.js";
8
- import { CoValueKnownState } from "../sync.js";
9
5
  import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
10
6
  import { isCoValue } from "../typeUtils/isCoValue.js";
11
7
  import { RawAccountID } from "./account.js";
@@ -39,12 +35,14 @@ type InsertionEntry<T extends JsonValue> = {
39
35
  madeAt: number;
40
36
  predecessors: OpID[];
41
37
  successors: OpID[];
42
- } & InsertionOpPayload<T>;
38
+ change: InsertionOpPayload<T>;
39
+ };
43
40
 
44
41
  type DeletionEntry = {
45
42
  madeAt: number;
46
43
  deletionID: OpID;
47
- } & DeletionOpPayload;
44
+ change: DeletionOpPayload;
45
+ };
48
46
 
49
47
  export class RawCoList<
50
48
  Item extends JsonValue = JsonValue,
@@ -109,6 +107,83 @@ export class RawCoList<
109
107
  this.processNewTransactions();
110
108
  }
111
109
 
110
+ private getInsertionsEntry(opID: OpID) {
111
+ const index = getSessionIndex(opID);
112
+
113
+ const sessionEntry = this.insertions[index];
114
+ if (!sessionEntry) {
115
+ return undefined;
116
+ }
117
+
118
+ const txEntry = sessionEntry[opID.txIndex];
119
+ if (!txEntry) {
120
+ return undefined;
121
+ }
122
+
123
+ return txEntry[opID.changeIdx];
124
+ }
125
+
126
+ private createInsertionsEntry(opID: OpID, value: InsertionEntry<Item>) {
127
+ const index = getSessionIndex(opID);
128
+
129
+ let sessionEntry = this.insertions[index];
130
+ if (!sessionEntry) {
131
+ sessionEntry = {};
132
+ this.insertions[index] = sessionEntry;
133
+ }
134
+
135
+ let txEntry = sessionEntry[opID.txIndex];
136
+ if (!txEntry) {
137
+ txEntry = {};
138
+ sessionEntry[opID.txIndex] = txEntry;
139
+ }
140
+
141
+ txEntry[opID.changeIdx] = value;
142
+ }
143
+
144
+ private isDeleted(opID: OpID) {
145
+ const index = getSessionIndex(opID);
146
+
147
+ const sessionEntry = this.deletionsByInsertion[index];
148
+
149
+ if (!sessionEntry) {
150
+ return false;
151
+ }
152
+
153
+ const txEntry = sessionEntry[opID.txIndex];
154
+
155
+ if (!txEntry) {
156
+ return false;
157
+ }
158
+
159
+ return Boolean(txEntry[opID.changeIdx]?.length);
160
+ }
161
+
162
+ private pushDeletionsByInsertionEntry(opID: OpID, value: DeletionEntry) {
163
+ const index = getSessionIndex(opID);
164
+
165
+ let sessionEntry = this.deletionsByInsertion[index];
166
+ if (!sessionEntry) {
167
+ sessionEntry = {};
168
+ this.deletionsByInsertion[index] = sessionEntry;
169
+ }
170
+
171
+ let txEntry = sessionEntry[opID.txIndex];
172
+ if (!txEntry) {
173
+ txEntry = {};
174
+ sessionEntry[opID.txIndex] = txEntry;
175
+ }
176
+
177
+ let list = txEntry[opID.changeIdx];
178
+
179
+ if (!list) {
180
+ list = [];
181
+ txEntry[opID.changeIdx] = list;
182
+ }
183
+
184
+ list.push(value);
185
+ }
186
+
112
187
  processNewTransactions() {
113
188
  const transactions = this.core.getValidSortedTransactions({
114
189
  ignorePrivateTransactions: false,
@@ -133,87 +208,51 @@ export class RawCoList<
133
208
  for (const [changeIdx, changeUntyped] of changes.entries()) {
134
209
  const change = changeUntyped as ListOpPayload<Item>;
135
210
 
211
+ const opID = {
212
+ sessionID: txID.sessionID,
213
+ txIndex: txID.txIndex,
214
+ branch: txID.branch,
215
+ changeIdx,
216
+ };
217
+
136
218
  if (change.op === "pre" || change.op === "app") {
137
- let sessionEntry = this.insertions[txID.sessionID];
138
- if (!sessionEntry) {
139
- sessionEntry = {};
140
- this.insertions[txID.sessionID] = sessionEntry;
141
- }
142
- let txEntry = sessionEntry[txID.txIndex];
143
- if (!txEntry) {
144
- txEntry = {};
145
- sessionEntry[txID.txIndex] = txEntry;
146
- }
147
- txEntry[changeIdx] = {
219
+ this.createInsertionsEntry(opID, {
148
220
  madeAt,
149
221
  predecessors: [],
150
222
  successors: [],
151
- ...change,
152
- };
223
+ change,
224
+ });
225
+
153
226
  if (change.op === "pre") {
154
227
  if (change.before === "end") {
155
- this.beforeEnd.push({
156
- ...txID,
157
- changeIdx,
158
- });
228
+ this.beforeEnd.push(opID);
159
229
  } else {
160
- const beforeEntry =
161
- this.insertions[change.before.sessionID]?.[
162
- change.before.txIndex
163
- ]?.[change.before.changeIdx];
230
+ const beforeEntry = this.getInsertionsEntry(change.before);
231
+
164
232
  if (!beforeEntry) {
165
233
  continue;
166
234
  }
167
- beforeEntry.predecessors.splice(0, 0, {
168
- ...txID,
169
- changeIdx,
170
- });
235
+
236
+ beforeEntry.predecessors.push(opID);
171
237
  }
172
238
  } else {
173
239
  if (change.after === "start") {
174
- this.afterStart.push({
175
- ...txID,
176
- changeIdx,
177
- });
240
+ this.afterStart.push(opID);
178
241
  } else {
179
- const afterEntry =
180
- this.insertions[change.after.sessionID]?.[
181
- change.after.txIndex
182
- ]?.[change.after.changeIdx];
242
+ const afterEntry = this.getInsertionsEntry(change.after);
243
+
183
244
  if (!afterEntry) {
184
245
  continue;
185
246
  }
186
- afterEntry.successors.push({
187
- ...txID,
188
- changeIdx,
189
- });
247
+
248
+ afterEntry.successors.push(opID);
190
249
  }
191
250
  }
192
251
  } else if (change.op === "del") {
193
- let sessionEntry =
194
- this.deletionsByInsertion[change.insertion.sessionID];
195
- if (!sessionEntry) {
196
- sessionEntry = {};
197
- this.deletionsByInsertion[change.insertion.sessionID] =
198
- sessionEntry;
199
- }
200
- let txEntry = sessionEntry[change.insertion.txIndex];
201
- if (!txEntry) {
202
- txEntry = {};
203
- sessionEntry[change.insertion.txIndex] = txEntry;
204
- }
205
- let changeEntry = txEntry[change.insertion.changeIdx];
206
- if (!changeEntry) {
207
- changeEntry = [];
208
- txEntry[change.insertion.changeIdx] = changeEntry;
209
- }
210
- changeEntry.push({
252
+ this.pushDeletionsByInsertionEntry(change.insertion, {
211
253
  madeAt,
212
- deletionID: {
213
- ...txID,
214
- changeIdx,
215
- },
216
- ...change,
254
+ deletionID: opID,
255
+ change,
217
256
  });
218
257
  } else {
219
258
  throw new Error(
@@ -324,10 +363,7 @@ export class RawCoList<
324
363
  while (todo.length > 0) {
325
364
  const currentOpID = todo[todo.length - 1]!;
326
365
 
327
- const entry =
328
- this.insertions[currentOpID.sessionID]?.[currentOpID.txIndex]?.[
329
- currentOpID.changeIdx
330
- ];
366
+ const entry = this.getInsertionsEntry(currentOpID);
331
367
 
332
368
  if (!entry) {
333
369
  throw new Error("Missing op " + currentOpID);
@@ -338,22 +374,19 @@ export class RawCoList<
338
374
 
339
375
  // We navigate the predecessors before processing the current opID in the list
340
376
  if (shouldTraversePredecessors) {
341
- for (let i = entry.predecessors.length - 1; i >= 0; i--) {
342
- todo.push(entry.predecessors[i]!);
377
+ for (const predecessor of entry.predecessors) {
378
+ todo.push(predecessor);
343
379
  }
344
380
  predecessorsVisited.add(currentOpID);
345
381
  } else {
346
382
  // Remove the current opID from the todo stack to consider it processed.
347
383
  todo.pop();
348
384
 
349
- const deleted =
350
- (this.deletionsByInsertion[currentOpID.sessionID]?.[
351
- currentOpID.txIndex
352
- ]?.[currentOpID.changeIdx]?.length || 0) > 0;
385
+ const deleted = this.isDeleted(currentOpID);
353
386
 
354
387
  if (!deleted) {
355
388
  arr.push({
356
- value: entry.value,
389
+ value: entry.change.value,
357
390
  madeAt: entry.madeAt,
358
391
  opID: currentOpID,
359
392
  });
@@ -629,3 +662,10 @@ export class RawCoList<
629
662
  this._cachedEntries = undefined;
630
663
  }
631
664
  }
665
+
666
+ function getSessionIndex(txID: TransactionID): SessionID {
667
+ if (txID.branch) {
668
+ return `${txID.sessionID}_branch_${txID.branch}`;
669
+ }
670
+ return txID.sessionID;
671
+ }
package/src/exports.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
2
2
  import { type RawCoValue } from "./coValue.js";
3
- import { CoValueCore, idforHeader } from "./coValueCore/coValueCore.js";
3
+ import {
4
+ CoValueCore,
5
+ idforHeader,
6
+ type AvailableCoValueCore,
7
+ } from "./coValueCore/coValueCore.js";
4
8
  import { CoValueUniqueness } from "./coValueCore/verifiedState.js";
5
9
  import {
6
10
  ControlledAccount,
@@ -171,6 +175,7 @@ export type {
171
175
  BinaryStreamStart,
172
176
  OpID,
173
177
  AccountRole,
178
+ AvailableCoValueCore,
174
179
  };
175
180
 
176
181
  export * from "./storage/index.js";
package/src/ids.ts CHANGED
@@ -23,6 +23,7 @@ export function rawCoIDfromBytes(bytes: Uint8Array): RawCoID {
23
23
  export type TransactionID = {
24
24
  sessionID: SessionID;
25
25
  txIndex: number;
26
+ branch?: RawCoID;
26
27
  };
27
28
 
28
29
  export type AgentID = `sealer_z${string}/signer_z${string}`;
@@ -110,10 +110,14 @@ export class StorageApiAsync implements StorageAPI {
110
110
 
111
111
  let idx = 0;
112
112
 
113
- signatures.push({
114
- idx: sessionRow.lastIdx,
115
- signature: sessionRow.lastSignature,
116
- });
113
+ const lastSignature = signatures[signatures.length - 1];
114
+
115
+ if (lastSignature?.signature !== sessionRow.lastSignature) {
116
+ signatures.push({
117
+ idx: sessionRow.lastIdx,
118
+ signature: sessionRow.lastSignature,
119
+ });
120
+ }
117
121
 
118
122
  for (const signature of signatures) {
119
123
  const newTxsInSession = await this.dbClient.getNewTransactionInSession(
@@ -113,10 +113,14 @@ export class StorageApiSync implements StorageAPI {
113
113
 
114
114
  let idx = 0;
115
115
 
116
- signatures.push({
117
- idx: sessionRow.lastIdx,
118
- signature: sessionRow.lastSignature,
119
- });
116
+ const lastSignature = signatures[signatures.length - 1];
117
+
118
+ if (lastSignature?.signature !== sessionRow.lastSignature) {
119
+ signatures.push({
120
+ idx: sessionRow.lastIdx,
121
+ signature: sessionRow.lastSignature,
122
+ });
123
+ }
120
124
 
121
125
  for (const signature of signatures) {
122
126
  const newTxsInSession = this.dbClient.getNewTransactionInSession(
@@ -4,7 +4,7 @@ import {
4
4
  setupTestNode,
5
5
  loadCoValueOrFail,
6
6
  } from "./testUtils.js";
7
- import { expectMap } from "../coValue.js";
7
+ import { expectList, expectMap, expectPlainText } from "../coValue.js";
8
8
 
9
9
  let jazzCloud: ReturnType<typeof setupTestNode>;
10
10
 
@@ -160,6 +160,108 @@ describe("Branching Logic", () => {
160
160
  expect(originalMap.get("key1")).toBe("branchValue1");
161
161
  expect(originalMap.get("key2")).toBe("branchValue2");
162
162
  });
163
+
164
+ test("should work with co.list", () => {
165
+ const node = createTestNode();
166
+ const group = node.createGroup();
167
+ const list = group.createList();
168
+
169
+ // Create a shopping list with grocery items
170
+ list.appendItems(["bread", "milk", "eggs"]);
171
+
172
+ // Remove milk from the list
173
+ list.delete(list.asArray().indexOf("milk"));
174
+
175
+ const branch = expectList(
176
+ list.core.createBranch("feature-branch", group.id).getCurrentContent(),
177
+ );
178
+
179
+ // Add more items to the branch
180
+ branch.appendItems(["cheese", "yogurt", "bananas"]);
181
+
182
+ // Remove yogurt from the branch
183
+ branch.delete(branch.asArray().indexOf("yogurt"));
184
+
185
+ const result = expectList(branch.core.mergeBranch().getCurrentContent());
186
+
187
+ expect(result.toJSON()).toEqual(["bread", "eggs", "cheese", "bananas"]);
188
+ });
189
+
190
+ test("should work with co.list when branching from different session", async () => {
191
+ const client = setupTestNode({
192
+ connected: true,
193
+ });
194
+ const group = client.node.createGroup();
195
+ const list = group.createList();
196
+
197
+ // Create a grocery list with initial items
198
+ list.appendItems(["bread", "milk"]);
199
+
200
+ const branch1 = expectList(
201
+ list.core.createBranch("feature-branch", group.id).getCurrentContent(),
202
+ );
203
+
204
+ // Add new items to first branch
205
+ branch1.appendItems(["cheese"]);
206
+
207
+ const branch2 = expectList(
208
+ list.core
209
+ .createBranch("feature-branch-2", group.id)
210
+ .getCurrentContent(),
211
+ );
212
+
213
+ // Add different items to second branch
214
+ branch2.appendItems(["apples", "oranges", "carrots"]);
215
+
216
+ const anotherSession = client.spawnNewSession();
217
+
218
+ const loadedBranch2 = await loadCoValueOrFail(
219
+ anotherSession.node,
220
+ branch2.id,
221
+ );
222
+
223
+ // Add more items and remove some existing ones
224
+ loadedBranch2.appendItems(["tomatoes", "lettuce", "cucumber"]);
225
+ loadedBranch2.delete(loadedBranch2.asArray().indexOf("lettuce"));
226
+ loadedBranch2.delete(loadedBranch2.asArray().indexOf("milk"));
227
+
228
+ loadedBranch2.core.mergeBranch();
229
+
230
+ await loadedBranch2.core.waitForSync();
231
+
232
+ branch1.core.mergeBranch();
233
+
234
+ expect(list.toJSON()).toEqual([
235
+ "bread",
236
+ "cheese",
237
+ "apples",
238
+ "oranges",
239
+ "carrots",
240
+ "tomatoes",
241
+ "cucumber",
242
+ ]);
243
+ });
244
+
245
+ test("should work with co.plainText when branching from different session", async () => {
246
+ const node = createTestNode();
247
+ const group = node.createGroup();
248
+ const plainText = group.createPlainText();
249
+
250
+ plainText.insertAfter(0, "hello");
251
+
252
+ const branch = expectPlainText(
253
+ plainText.core
254
+ .createBranch("feature-branch", group.id)
255
+ .getCurrentContent(),
256
+ );
257
+
258
+ // Add more items to the branch
259
+ branch.insertAfter("hello".length, "world");
260
+
261
+ branch.core.mergeBranch();
262
+
263
+ expect(plainText.toString()).toEqual("helloworld");
264
+ });
163
265
  });
164
266
 
165
267
  describe("Branch Loading and Checkout", () => {