cojson 0.2.3 → 0.3.0-alpha.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 (78) hide show
  1. package/dist/account.d.ts +1 -1
  2. package/dist/coValue.d.ts +5 -13
  3. package/dist/coValue.js +14 -7
  4. package/dist/coValue.js.map +1 -1
  5. package/dist/coValueCore.d.ts +6 -6
  6. package/dist/coValueCore.js +11 -14
  7. package/dist/coValueCore.js.map +1 -1
  8. package/dist/coValues/coList.d.ts +99 -34
  9. package/dist/coValues/coList.js +162 -72
  10. package/dist/coValues/coList.js.map +1 -1
  11. package/dist/coValues/coMap.d.ts +96 -31
  12. package/dist/coValues/coMap.js +157 -114
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.d.ts +67 -23
  15. package/dist/coValues/coStream.js +131 -59
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/crypto.d.ts +8 -3
  18. package/dist/crypto.js +6 -6
  19. package/dist/crypto.js.map +1 -1
  20. package/dist/group.d.ts +57 -23
  21. package/dist/group.js +75 -33
  22. package/dist/group.js.map +1 -1
  23. package/dist/index.d.ts +8 -6
  24. package/dist/index.js +8 -8
  25. package/dist/index.js.map +1 -1
  26. package/dist/{node.d.ts → localNode.d.ts} +16 -8
  27. package/dist/{node.js → localNode.js} +48 -40
  28. package/dist/localNode.js.map +1 -0
  29. package/dist/permissions.js +6 -3
  30. package/dist/permissions.js.map +1 -1
  31. package/dist/queriedCoValues/queriedCoList.d.ts +66 -0
  32. package/dist/queriedCoValues/queriedCoList.js +120 -0
  33. package/dist/queriedCoValues/queriedCoList.js.map +1 -0
  34. package/dist/queriedCoValues/queriedCoMap.d.ts +47 -0
  35. package/dist/queriedCoValues/queriedCoMap.js +83 -0
  36. package/dist/queriedCoValues/queriedCoMap.js.map +1 -0
  37. package/dist/queriedCoValues/queriedCoStream.d.ts +40 -0
  38. package/dist/queriedCoValues/queriedCoStream.js +72 -0
  39. package/dist/queriedCoValues/queriedCoStream.js.map +1 -0
  40. package/dist/queries.d.ts +29 -112
  41. package/dist/queries.js +44 -227
  42. package/dist/queries.js.map +1 -1
  43. package/dist/sync.d.ts +1 -1
  44. package/dist/sync.js +1 -1
  45. package/dist/sync.js.map +1 -1
  46. package/dist/tests/testUtils.d.ts +1 -1
  47. package/dist/tests/testUtils.js +3 -3
  48. package/dist/tests/testUtils.js.map +1 -1
  49. package/package.json +2 -2
  50. package/src/account.ts +1 -1
  51. package/src/coValue.ts +25 -20
  52. package/src/coValueCore.ts +17 -21
  53. package/src/coValues/coList.ts +242 -128
  54. package/src/coValues/coMap.ts +293 -162
  55. package/src/coValues/coStream.ts +227 -94
  56. package/src/crypto.ts +37 -24
  57. package/src/group.ts +90 -63
  58. package/src/index.ts +35 -25
  59. package/src/{node.ts → localNode.ts} +64 -64
  60. package/src/permissions.ts +15 -18
  61. package/src/queriedCoValues/queriedCoList.ts +248 -0
  62. package/src/queriedCoValues/queriedCoMap.ts +180 -0
  63. package/src/queriedCoValues/queriedCoStream.ts +125 -0
  64. package/src/queries.ts +83 -460
  65. package/src/sync.ts +2 -2
  66. package/src/tests/account.test.ts +3 -6
  67. package/src/tests/coValue.test.ts +116 -110
  68. package/src/tests/coValueCore.test.ts +1 -1
  69. package/src/tests/crypto.test.ts +19 -21
  70. package/src/tests/permissions.test.ts +255 -242
  71. package/src/tests/queries.test.ts +57 -40
  72. package/src/tests/sync.test.ts +30 -30
  73. package/src/tests/testUtils.ts +3 -3
  74. package/dist/coValues/static.d.ts +0 -14
  75. package/dist/coValues/static.js +0 -20
  76. package/dist/coValues/static.js.map +0 -1
  77. package/dist/node.js.map +0 -1
  78. package/src/coValues/static.ts +0 -31
@@ -1,6 +1,5 @@
1
1
  import { randomBytes } from "@noble/hashes/utils";
2
- import { AnyCoValue } from "./coValue.js";
3
- import { Static } from "./coValues/static.js";
2
+ import { AnyCoValue, CoValue } from "./coValue.js";
4
3
  import { BinaryCoStream, CoStream } from "./coValues/coStream.js";
5
4
  import { CoMap } from "./coValues/coMap.js";
6
5
  import {
@@ -28,7 +27,7 @@ import {
28
27
  isKeyForKeyField,
29
28
  } from "./permissions.js";
30
29
  import { Group, expectGroupContent } from "./group.js";
31
- import { LocalNode } from "./node.js";
30
+ import { LocalNode } from "./localNode.js";
32
31
  import { CoValueKnownState, NewContentMessage } from "./sync.js";
33
32
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
34
33
  import { CoList } from "./coValues/coList.js";
@@ -99,8 +98,8 @@ export class CoValueCore {
99
98
  node: LocalNode;
100
99
  header: CoValueHeader;
101
100
  _sessions: { [key: SessionID]: SessionLog };
102
- _cachedContent?: AnyCoValue;
103
- listeners: Set<(content?: AnyCoValue) => void> = new Set();
101
+ _cachedContent?: CoValue;
102
+ listeners: Set<(content?: CoValue) => void> = new Set();
104
103
  _decryptionCache: {
105
104
  [key: Encrypted<JsonValue[], JsonValue>]:
106
105
  | Stringified<JsonValue[]>
@@ -376,7 +375,7 @@ export class CoValueCore {
376
375
  }
377
376
  }
378
377
 
379
- subscribe(listener: (content?: AnyCoValue) => void): () => void {
378
+ subscribe(listener: (content?: CoValue) => void): () => void {
380
379
  this.listeners.add(listener);
381
380
  listener(this.getCurrentContent());
382
381
 
@@ -487,13 +486,13 @@ export class CoValueCore {
487
486
  );
488
487
 
489
488
  if (success) {
490
- void this.node.sync.syncCoValue(this);
489
+ void this.node.syncManager.syncCoValue(this);
491
490
  }
492
491
 
493
492
  return success;
494
493
  }
495
494
 
496
- getCurrentContent(): AnyCoValue {
495
+ getCurrentContent(): CoValue {
497
496
  if (this._cachedContent) {
498
497
  return this._cachedContent;
499
498
  }
@@ -508,8 +507,6 @@ export class CoValueCore {
508
507
  } else {
509
508
  this._cachedContent = new CoStream(this);
510
509
  }
511
- } else if (this.header.type === "static") {
512
- this._cachedContent = new Static(this);
513
510
  } else {
514
511
  throw new Error(`Unknown coValue type ${this.header.type}`);
515
512
  }
@@ -611,26 +608,24 @@ export class CoValueCore {
611
608
 
612
609
  // Try to find key revelation for us
613
610
 
614
- const readKeyEntry = content.getLastEntry(
611
+ const lastReadyKeyEdit = content.lastEditAt(
615
612
  `${keyID}_for_${this.node.account.id}`
616
613
  );
617
614
 
618
- if (readKeyEntry) {
619
- const revealer = accountOrAgentIDfromSessionID(
620
- readKeyEntry.txID.sessionID
621
- );
615
+ if (lastReadyKeyEdit?.value) {
616
+ const revealer = lastReadyKeyEdit.by;
622
617
  const revealerAgent = this.node.resolveAccountAgent(
623
618
  revealer,
624
619
  "Expected to know revealer"
625
620
  );
626
621
 
627
622
  const secret = unseal(
628
- readKeyEntry.value,
623
+ lastReadyKeyEdit.value,
629
624
  this.node.account.currentSealerSecret(),
630
625
  getAgentSealerID(revealerAgent),
631
626
  {
632
627
  in: this.id,
633
- tx: readKeyEntry.txID,
628
+ tx: lastReadyKeyEdit.tx,
634
629
  }
635
630
  );
636
631
 
@@ -784,15 +779,16 @@ export class CoValueCore {
784
779
  sessionEntry = {
785
780
  after: sentState[sessionID] ?? 0,
786
781
  newTransactions: [],
787
- lastSignature: "WILL_BE_REPLACED" as Signature
782
+ lastSignature: "WILL_BE_REPLACED" as Signature,
788
783
  };
789
784
  currentPiece.new[sessionID] = sessionEntry;
790
785
  }
791
786
 
792
787
  sessionEntry.newTransactions.push(...txsToAdd);
793
- sessionEntry.lastSignature = nextKnownSignatureIdx === undefined
794
- ? log.lastSignature!
795
- : log.signatureAfter[nextKnownSignatureIdx]!
788
+ sessionEntry.lastSignature =
789
+ nextKnownSignatureIdx === undefined
790
+ ? log.lastSignature!
791
+ : log.signatureAfter[nextKnownSignatureIdx]!;
796
792
 
797
793
  sentState[sessionID] =
798
794
  (sentState[sessionID] || 0) + txsToAdd.length;
@@ -1,9 +1,9 @@
1
1
  import { JsonObject, JsonValue } from "../jsonValue.js";
2
2
  import { CoID, CoValue, isCoValue } from "../coValue.js";
3
3
  import { CoValueCore, accountOrAgentIDfromSessionID } from "../coValueCore.js";
4
- import { SessionID, TransactionID } from "../ids.js";
4
+ import { AgentID, SessionID, TransactionID } from "../ids.js";
5
5
  import { Group } from "../group.js";
6
- import { AccountID, isAccountID } from "../account.js";
6
+ import { AccountID } from "../account.js";
7
7
  import { parseJSON } from "../jsonStringify.js";
8
8
 
9
9
  type OpID = TransactionID & { changeIdx: number };
@@ -40,13 +40,16 @@ type DeletionEntry = {
40
40
  deletionID: OpID;
41
41
  } & DeletionOpPayload;
42
42
 
43
- export class CoList<
44
- T extends JsonValue | CoValue,
43
+ export class CoListView<
44
+ Item extends JsonValue | CoValue,
45
45
  Meta extends JsonObject | null = null
46
46
  > implements CoValue
47
47
  {
48
+ /** @category 6. Meta */
48
49
  id: CoID<this>;
50
+ /** @category 6. Meta */
49
51
  type = "colist" as const;
52
+ /** @category 6. Meta */
50
53
  core: CoValueCore;
51
54
  /** @internal */
52
55
  afterStart: OpID[];
@@ -56,7 +59,7 @@ export class CoList<
56
59
  insertions: {
57
60
  [sessionID: SessionID]: {
58
61
  [txIdx: number]: {
59
- [changeIdx: number]: InsertionEntry<T>;
62
+ [changeIdx: number]: InsertionEntry<Item>;
60
63
  };
61
64
  };
62
65
  };
@@ -68,6 +71,8 @@ export class CoList<
68
71
  };
69
72
  };
70
73
  };
74
+ /** @category 6. Meta */
75
+ readonly _item!: Item;
71
76
 
72
77
  /** @internal */
73
78
  constructor(core: CoValueCore) {
@@ -78,19 +83,6 @@ export class CoList<
78
83
  this.insertions = {};
79
84
  this.deletionsByInsertion = {};
80
85
 
81
- this.fillOpsFromCoValue();
82
- }
83
-
84
- get meta(): Meta {
85
- return this.core.header.meta as Meta;
86
- }
87
-
88
- get group(): Group {
89
- return this.core.getGroup();
90
- }
91
-
92
- /** @internal */
93
- protected fillOpsFromCoValue() {
94
86
  this.insertions = {};
95
87
  this.deletionsByInsertion = {};
96
88
  this.afterStart = [];
@@ -104,7 +96,7 @@ export class CoList<
104
96
  for (const [changeIdx, changeUntyped] of parseJSON(
105
97
  changes
106
98
  ).entries()) {
107
- const change = changeUntyped as ListOpPayload<T>;
99
+ const change = changeUntyped as ListOpPayload<Item>;
108
100
 
109
101
  if (change.op === "pre" || change.op === "app") {
110
102
  let sessionEntry = this.insertions[txID.sessionID];
@@ -204,10 +196,35 @@ export class CoList<
204
196
  }
205
197
  }
206
198
 
207
- /** Get the item currently at `idx`. */
199
+ /** @category 6. Meta */
200
+ get meta(): Meta {
201
+ return this.core.header.meta as Meta;
202
+ }
203
+
204
+ /** @category 6. Meta */
205
+ get group(): Group {
206
+ return this.core.getGroup();
207
+ }
208
+
209
+ /**
210
+ * Not yet implemented
211
+ *
212
+ * @category 4. Time travel
213
+ */
214
+ atTime(_time: number): this {
215
+ throw new Error("Not yet implemented");
216
+ }
217
+
218
+ /**
219
+ * Get the item currently at `idx`.
220
+ *
221
+ * @category 1. Reading
222
+ */
208
223
  get(
209
224
  idx: number
210
- ): (T extends CoValue ? CoID<T> : Exclude<T, CoValue>) | undefined {
225
+ ):
226
+ | (Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>)
227
+ | undefined {
211
228
  const entry = this.entries()[idx];
212
229
  if (!entry) {
213
230
  return undefined;
@@ -215,18 +232,23 @@ export class CoList<
215
232
  return entry.value;
216
233
  }
217
234
 
218
- /** Returns the current items in the CoList as an array. */
219
- asArray(): (T extends CoValue ? CoID<T> : Exclude<T, CoValue>)[] {
235
+ /**
236
+ * Returns the current items in the CoList as an array.
237
+ *
238
+ * @category 1. Reading
239
+ **/
240
+ asArray(): (Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>)[] {
220
241
  return this.entries().map((entry) => entry.value);
221
242
  }
222
243
 
223
- entries(): {
224
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>;
244
+ /** @internal */
245
+ entries(): {
246
+ value: Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>;
225
247
  madeAt: number;
226
248
  opID: OpID;
227
249
  }[] {
228
250
  const arr: {
229
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>;
251
+ value: Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>;
230
252
  madeAt: number;
231
253
  opID: OpID;
232
254
  }[] = [];
@@ -243,7 +265,7 @@ export class CoList<
243
265
  private fillArrayFromOpID(
244
266
  opID: OpID,
245
267
  arr: {
246
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>;
268
+ value: Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>;
247
269
  madeAt: number;
248
270
  opID: OpID;
249
271
  }[]
@@ -272,101 +294,115 @@ export class CoList<
272
294
  }
273
295
  }
274
296
 
275
- /** Returns the accountID of the account that inserted value at the given index. */
276
- whoInserted(idx: number): AccountID | undefined {
297
+ /**
298
+ * Returns the current items in the CoList as an array. (alias of `asArray`)
299
+ *
300
+ * @category 1. Reading
301
+ */
302
+ toJSON(): (Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>)[] {
303
+ return this.asArray();
304
+ }
305
+
306
+ /** @category 5. Edit history */
307
+ editAt(idx: number):
308
+ | {
309
+ by: AccountID | AgentID;
310
+ tx: TransactionID;
311
+ at: Date;
312
+ value: Item extends CoValue ? CoID<Item> : Exclude<Item, CoValue>;
313
+ }
314
+ | undefined {
277
315
  const entry = this.entries()[idx];
278
316
  if (!entry) {
279
317
  return undefined;
280
318
  }
281
- const accountID = accountOrAgentIDfromSessionID(entry.opID.sessionID);
282
- if (isAccountID(accountID)) {
283
- return accountID;
284
- } else {
285
- return undefined;
286
- }
287
- }
288
-
289
- /** Returns the current items in the CoList as an array. (alias of `asArray`) */
290
- toJSON(): (T extends CoValue ? CoID<T> : Exclude<T, CoValue>)[] {
291
- return this.asArray();
319
+ const madeAt = new Date(entry.madeAt);
320
+ const by = accountOrAgentIDfromSessionID(entry.opID.sessionID);
321
+ const value = entry.value;
322
+ return {
323
+ by,
324
+ tx: {
325
+ sessionID: entry.opID.sessionID,
326
+ txIndex: entry.opID.txIndex,
327
+ },
328
+ at: madeAt,
329
+ value,
330
+ };
292
331
  }
293
332
 
294
- map<U>(
295
- mapper: (
296
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>,
297
- idx: number
298
- ) => U
299
- ): U[] {
300
- return this.entries().map((entry, idx) => mapper(entry.value, idx));
301
- }
333
+ /** @category 5. Edit history */
334
+ deletionEdits(): {
335
+ by: AccountID | AgentID;
336
+ tx: TransactionID;
337
+ at: Date;
338
+ // TODO: add indices that are now before and after the deleted item
339
+ }[] {
340
+ const edits: {
341
+ by: AccountID | AgentID;
342
+ tx: TransactionID;
343
+ at: Date;
344
+ }[] = [];
302
345
 
303
- filter<U extends T extends CoValue ? CoID<T> : Exclude<T, CoValue>>(
304
- predicate: (
305
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>,
306
- idx: number
307
- ) => value is U
308
- ): U[];
309
- filter(
310
- predicate: (
311
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>,
312
- idx: number
313
- ) => boolean
314
- ): (T extends CoValue ? CoID<T> : Exclude<T, CoValue>)[] {
315
- return this.entries()
316
- .filter((entry, idx) => predicate(entry.value, idx))
317
- .map((entry) => entry.value);
318
- }
346
+ for (const sessionID in this.deletionsByInsertion) {
347
+ const sessionEntry =
348
+ this.deletionsByInsertion[sessionID as SessionID];
349
+ for (const txIdx in sessionEntry) {
350
+ const txEntry = sessionEntry[Number(txIdx)];
351
+ for (const changeIdx in txEntry) {
352
+ const changeEntry = txEntry[Number(changeIdx)];
353
+ for (const deletion of changeEntry || []) {
354
+ const madeAt = new Date(deletion.madeAt);
355
+ const by = accountOrAgentIDfromSessionID(
356
+ deletion.deletionID.sessionID
357
+ );
358
+ edits.push({
359
+ by,
360
+ tx: deletion.deletionID,
361
+ at: madeAt,
362
+ });
363
+ }
364
+ }
365
+ }
366
+ }
319
367
 
320
- reduce<U>(
321
- reducer: (
322
- accumulator: U,
323
- value: T extends CoValue ? CoID<T> : Exclude<T, CoValue>,
324
- idx: number
325
- ) => U,
326
- initialValue: U
327
- ): U {
328
- return this.entries().reduce(
329
- (accumulator, entry, idx) => reducer(accumulator, entry.value, idx),
330
- initialValue
331
- );
368
+ return edits;
332
369
  }
333
370
 
371
+ /** @category 3. Subscription */
334
372
  subscribe(listener: (coList: this) => void): () => void {
335
373
  return this.core.subscribe((content) => {
336
374
  listener(content as this);
337
375
  });
338
376
  }
339
-
340
- edit(changer: (editable: WriteableCoList<T, Meta>) => void): this {
341
- const editable = new WriteableCoList<T, Meta>(this.core);
342
- changer(editable);
343
- return new CoList(this.core) as this;
344
- }
345
377
  }
346
378
 
347
- export class WriteableCoList<
348
- T extends JsonValue | CoValue,
379
+ export class CoList<
380
+ Item extends JsonValue | CoValue,
349
381
  Meta extends JsonObject | null = null
350
382
  >
351
- extends CoList<T, Meta>
383
+ extends CoListView<Item, Meta>
352
384
  implements CoValue
353
385
  {
354
- /** @internal */
355
- edit(_changer: (editable: WriteableCoList<T, Meta>) => void): this {
356
- throw new Error("Already editing.");
357
- }
358
-
359
- /** Appends a new item after index `after`.
386
+ /** Returns a new version of this CoList with `item` appended after the item currently at index `after`.
360
387
  *
361
- * If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
388
+ * If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
362
389
  *
363
- * If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
390
+ * If `privacy` is `"trusting"`, `item` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
391
+ *
392
+ * @category 2. Editing
393
+ **/
364
394
  append(
365
- after: number,
366
- value: T extends CoValue ? T | CoID<T> : T,
395
+ item: Item extends CoValue ? Item | CoID<Item> : Item,
396
+ after?: number,
367
397
  privacy: "private" | "trusting" = "private"
368
- ): void {
398
+ ): this {
369
399
  const entries = this.entries();
400
+ after =
401
+ after === undefined
402
+ ? entries.length > 0
403
+ ? entries.length - 1
404
+ : 0
405
+ : 0;
370
406
  let opIDBefore;
371
407
  if (entries.length > 0) {
372
408
  const entryBefore = entries[after];
@@ -384,47 +420,32 @@ export class WriteableCoList<
384
420
  [
385
421
  {
386
422
  op: "app",
387
- value: isCoValue(value) ? value.id : value,
423
+ value: isCoValue(item) ? item.id : item,
388
424
  after: opIDBefore,
389
425
  },
390
426
  ],
391
427
  privacy
392
428
  );
393
429
 
394
- this.fillOpsFromCoValue();
395
- }
396
-
397
- /** Pushes a new item to the end of the list.
398
- *
399
- * If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
400
- *
401
- * If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
402
- push(
403
- value: T extends CoValue ? T | CoID<T> : T,
404
- privacy: "private" | "trusting" = "private"
405
- ): void {
406
- // TODO: optimize
407
- const entries = this.entries();
408
- this.append(
409
- entries.length > 0 ? entries.length - 1 : 0,
410
- value,
411
- privacy
412
- );
430
+ return new CoList(this.core) as this;
413
431
  }
414
432
 
415
433
  /**
416
- * Prepends a new item before index `before`.
434
+ * Returns a new version of this CoList with `item` prepended before the item currently at index `before`.
417
435
  *
418
- * If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
436
+ * If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
419
437
  *
420
- * If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
438
+ * If `privacy` is `"trusting"`, `item` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
439
+ *
440
+ * @category 2. Editing
421
441
  */
422
442
  prepend(
423
- before: number,
424
- value: T extends CoValue ? T | CoID<T> : T,
443
+ item: Item extends CoValue ? Item | CoID<Item> : Item,
444
+ before?: number,
425
445
  privacy: "private" | "trusting" = "private"
426
- ): void {
446
+ ): this {
427
447
  const entries = this.entries();
448
+ before = before === undefined ? 0 : before;
428
449
  let opIDAfter;
429
450
  if (entries.length > 0) {
430
451
  const entryAfter = entries[before];
@@ -446,22 +467,25 @@ export class WriteableCoList<
446
467
  [
447
468
  {
448
469
  op: "pre",
449
- value: isCoValue(value) ? value.id : value,
470
+ value: isCoValue(item) ? item.id : item,
450
471
  before: opIDAfter,
451
472
  },
452
473
  ],
453
474
  privacy
454
475
  );
455
476
 
456
- this.fillOpsFromCoValue();
477
+ return new CoList(this.core) as this;
457
478
  }
458
479
 
459
- /** Deletes the item at index `at` from the list.
480
+ /** Returns a new version of this CoList with the item at index `at` deleted from the list.
460
481
  *
461
482
  * If `privacy` is `"private"` **(default)**, the fact of this deletion is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
462
483
  *
463
- * If `privacy` is `"trusting"`, the fact of this deletion is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
464
- delete(at: number, privacy: "private" | "trusting" = "private"): void {
484
+ * If `privacy` is `"trusting"`, the fact of this deletion is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
485
+ *
486
+ * @category 2. Editing
487
+ **/
488
+ delete(at: number, privacy: "private" | "trusting" = "private"): this {
465
489
  const entries = this.entries();
466
490
  const entry = entries[at];
467
491
  if (!entry) {
@@ -477,6 +501,96 @@ export class WriteableCoList<
477
501
  privacy
478
502
  );
479
503
 
480
- this.fillOpsFromCoValue();
504
+ return new CoList(this.core) as this;
505
+ }
506
+
507
+ /** @category 2. Editing */
508
+ mutate(mutator: (mutable: MutableCoList<Item, Meta>) => void): this {
509
+ const mutable = new MutableCoList<Item, Meta>(this.core);
510
+ mutator(mutable);
511
+ return new CoList(this.core) as this;
512
+ }
513
+
514
+ /** @deprecated Use `mutate` instead. */
515
+ edit(mutator: (mutable: MutableCoList<Item, Meta>) => void): this {
516
+ return this.mutate(mutator);
517
+ }
518
+ }
519
+
520
+ export class MutableCoList<
521
+ Item extends JsonValue | CoValue,
522
+ Meta extends JsonObject | null = null
523
+ >
524
+ extends CoListView<Item, Meta>
525
+ implements CoValue
526
+ {
527
+ /** Appends `item` after the item currently at index `after`.
528
+ *
529
+ * If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
530
+ *
531
+ * If `privacy` is `"trusting"`, `item` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
532
+ *
533
+ * @category 2. Mutating
534
+ **/
535
+ append(
536
+ item: Item extends CoValue ? Item | CoID<Item> : Item,
537
+ after?: number,
538
+ privacy: "private" | "trusting" = "private"
539
+ ): void {
540
+ const listAfter = CoList.prototype.append.call(
541
+ this,
542
+ item,
543
+ after,
544
+ privacy
545
+ ) as CoList<Item, Meta>;
546
+ this.afterStart = listAfter.afterStart;
547
+ this.beforeEnd = listAfter.beforeEnd;
548
+ this.insertions = listAfter.insertions;
549
+ this.deletionsByInsertion = listAfter.deletionsByInsertion;
550
+ }
551
+
552
+ /** Prepends `item` before the item currently at index `before`.
553
+ *
554
+ * If `privacy` is `"private"` **(default)**, `item` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
555
+ *
556
+ * If `privacy` is `"trusting"`, `item` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
557
+ *
558
+ * * @category 2. Mutating
559
+ **/
560
+ prepend(
561
+ item: Item extends CoValue ? Item | CoID<Item> : Item,
562
+ before?: number,
563
+ privacy: "private" | "trusting" = "private"
564
+ ): void {
565
+ const listAfter = CoList.prototype.prepend.call(
566
+ this,
567
+ item,
568
+ before,
569
+ privacy
570
+ ) as CoList<Item, Meta>;
571
+ this.afterStart = listAfter.afterStart;
572
+ this.beforeEnd = listAfter.beforeEnd;
573
+ this.insertions = listAfter.insertions;
574
+ this.deletionsByInsertion = listAfter.deletionsByInsertion;
575
+ }
576
+
577
+ /** Deletes the item at index `at` from the list.
578
+ *
579
+ * If `privacy` is `"private"` **(default)**, the fact of this deletion is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
580
+ *
581
+ * If `privacy` is `"trusting"`, the fact of this deletion is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
582
+ *
583
+ * * @category 2. Mutating
584
+ **/
585
+ delete(at: number, privacy: "private" | "trusting" = "private"): void {
586
+ const listAfter = CoList.prototype.delete.call(
587
+ this,
588
+ at,
589
+ privacy
590
+ ) as CoList<Item, Meta>;
591
+ this.afterStart = listAfter.afterStart;
592
+ this.beforeEnd = listAfter.beforeEnd;
593
+ this.insertions = listAfter.insertions;
594
+ this.deletionsByInsertion = listAfter.deletionsByInsertion;
481
595
  }
482
596
  }