lib0 1.0.0-rc.30 → 1.0.0-rc.32

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.
@@ -85,6 +85,10 @@ export const $deltaMapChangeJson: s.Schema<DeltaAttrOpJSON>;
85
85
  * fingerprint read (and any `diff` / equality check that relies on it) is
86
86
  * wrong. Fields covered: insert, delete, retain, format, attribution,
87
87
  * value, key.
88
+ * - **A `clone` copies `_fingerprint` when the copy is content-identical:** a full range and the
89
+ * same key. Freezing a nested value with `done()` is fingerprint-neutral (trailing plain retains
90
+ * are not fingerprinted), so nothing else can differ. The root delta returned by
91
+ * `clone`/`slice`/`cloneDeep` never inherits a fingerprint.
88
92
  *
89
93
  * @internal not part of the consumer API — a content op; build deltas via {@link create}/{@link DeltaBuilder}.
90
94
  */
@@ -433,9 +437,12 @@ export class SetAttrOp<V extends unknown = any, K extends string | number = any>
433
437
  * Full (frozen) clone. A `move` apply reuses the source op instead of cloning, so there is no
434
438
  * shared-mutable variant — see {@link DeltaBuilder#apply}.
435
439
  *
436
- * @return {SetAttrOp<V,K>}
440
+ * @template {string|number} [K2=K]
441
+ * @param {K2} [key] retarget the clone to another attribute key (a transformer projecting or
442
+ * renaming an attribute); defaults to this op's key.
443
+ * @return {SetAttrOp<V,K2>}
437
444
  */
438
- clone(): SetAttrOp<V, K>;
445
+ clone<K2 extends string | number = K>(key?: K2): SetAttrOp<V, K2>;
439
446
  $type: s.Schema<SetAttrOp<any, any>>;
440
447
  /**
441
448
  * @param {SetAttrOp<V>} other
@@ -475,9 +482,11 @@ export class DeleteAttrOp<V = any, K extends string | number = string | number>
475
482
  /**
476
483
  * Full (frozen) clone; a `move` apply reuses the source op instead — see {@link DeltaBuilder#apply}.
477
484
  *
478
- * @return {DeleteAttrOp<V,K>}
485
+ * @template {string|number} [K2=K]
486
+ * @param {K2} [key] retarget the clone to another attribute key; defaults to this op's key.
487
+ * @return {DeleteAttrOp<V,K2>}
479
488
  */
480
- clone(): DeleteAttrOp<V, K>;
489
+ clone<K2 extends string | number = K>(key?: K2): DeleteAttrOp<V, K2>;
481
490
  $type: s.Schema<DeleteAttrOp<any, string | number>>;
482
491
  /**
483
492
  * @param {DeleteAttrOp<V>} other
@@ -531,9 +540,11 @@ export class ModifyAttrOp<Modifier extends DeltaAny = DeltaAny, K extends string
531
540
  /**
532
541
  * Full (frozen) clone; a `move` apply reuses the source op instead — see {@link DeltaBuilder#apply}.
533
542
  *
534
- * @return {ModifyAttrOp<Modifier,K>}
543
+ * @template {string|number} [K2=K]
544
+ * @param {K2} [key] retarget the clone to another attribute key; defaults to this op's key.
545
+ * @return {ModifyAttrOp<Modifier,K2>}
535
546
  */
536
- clone(): ModifyAttrOp<Modifier, K>;
547
+ clone<K2 extends string | number = K>(key?: K2): ModifyAttrOp<Modifier, K2>;
537
548
  $type: s.Schema<ModifyAttrOp<any, string>>;
538
549
  /**
539
550
  * @param {ModifyAttrOp<Modifier>} other
@@ -545,7 +556,7 @@ export const $modifyOp: s.Schema<ModifyOp<Delta<any>>>;
545
556
  export const $textOp: s.Schema<TextOp>;
546
557
  export const $deleteOp: s.Schema<DeleteOp<any>>;
547
558
  export const $retainOp: s.Schema<RetainOp>;
548
- export const $anyOp: s.Schema<DeleteOp<any> | TextOp | InsertOp<any> | ModifyOp<Delta<any>>>;
559
+ export const $anyOp: s.Schema<TextOp | InsertOp<any> | DeleteOp<any> | ModifyOp<Delta<any>>>;
549
560
  export const $setAttrOp: s.Schema<SetAttrOp<any>>;
550
561
  export const $modifyAttrOp: s.Schema<ModifyAttrOp<any>>;
551
562
  export const $deleteAttrOp: s.Schema<DeleteAttrOp<any>>;
@@ -1263,9 +1274,20 @@ export type DeltaConfigGetRecursiveAttrs<Conf_1 extends DeltaConf> = Conf_1 exte
1263
1274
  recursiveAttrs: true;
1264
1275
  } ? true : false;
1265
1276
  /**
1266
- * Transform Delta(Builder) to a normal delta.
1267
- */
1268
- export type _SanifyDelta<V> = V extends never ? never : (import("../ts.js").TypeIsAny<V, any, V extends Delta<infer Conf_1> ? Delta<Conf_1> : V>);
1277
+ * Transform Delta(Builder) to a normal delta: a `DeltaBuilder<Conf>` becomes `Delta<Conf>`, anything
1278
+ * else passes through.
1279
+ *
1280
+ * Infers the conf from `DeltaBuilder` on purpose, not from `Delta<infer Conf>`. lib0's own source
1281
+ * check never had a problem with the latter, but a consumer type-checks the emitted declaration
1282
+ * files, and there matching a builder against `Delta<infer Conf>` is structural: TypeScript walks
1283
+ * `Delta`'s members and recurses through {@link DeltaConfGetChildren}'s recursive branch until its
1284
+ * instantiation-depth limit, so every nested condensed builder (for example
1285
+ * `create().insert([create('c', {}, [create('p', {}, 'x')])])`) failed with TS2589 in
1286
+ * y-prosemirror and yjs. The structural walk also leaked the grandchild conf into the child
1287
+ * union; inferring from the builder keeps the nesting exact. dist-check/consumer.js pins both
1288
+ * against dist.
1289
+ */
1290
+ export type _SanifyDelta<V> = V extends never ? never : (import("../ts.js").TypeIsAny<V, any, V extends DeltaBuilder<infer Conf_1, any> ? Delta<Conf_1> : V>);
1269
1291
  export type PrettifyDeltaConf<Conf_1 extends DeltaConf> = import("../ts.js").Prettify<{ [K in keyof Conf_1]: K extends "attrs" ? import("../ts.js").Prettify<{ [KA in keyof Conf_1[K]]: _SanifyDelta<Conf_1[K][KA]>; }, 1> : (K extends "children" ? _SanifyDelta<Conf_1[K]> : Conf_1[K]); }, 1>;
1270
1292
  export type DeltaConfOverwrite<D1 extends DeltaConf, D2> = (import("../ts.js").TypeIsAny<D1, any, PrettifyDeltaConf<{ [K in (keyof D1 | keyof D2)]: K extends keyof D2 ? D2[K] : (K extends keyof D1 ? D1[K] : never); }>> & {}) extends infer DC extends DeltaConf ? DC : never;
1271
1293
  export type ReadableDeltaConf = {
@@ -1512,10 +1534,21 @@ declare class Mark {
1512
1534
  * @typedef {Conf extends {recursiveAttrs:true} ? true : false} DeltaConfigGetRecursiveAttrs
1513
1535
  */
1514
1536
  /**
1515
- * Transform Delta(Builder) to a normal delta.
1537
+ * Transform Delta(Builder) to a normal delta: a `DeltaBuilder<Conf>` becomes `Delta<Conf>`, anything
1538
+ * else passes through.
1539
+ *
1540
+ * Infers the conf from `DeltaBuilder` on purpose, not from `Delta<infer Conf>`. lib0's own source
1541
+ * check never had a problem with the latter, but a consumer type-checks the emitted declaration
1542
+ * files, and there matching a builder against `Delta<infer Conf>` is structural: TypeScript walks
1543
+ * `Delta`'s members and recurses through {@link DeltaConfGetChildren}'s recursive branch until its
1544
+ * instantiation-depth limit, so every nested condensed builder (for example
1545
+ * `create().insert([create('c', {}, [create('p', {}, 'x')])])`) failed with TS2589 in
1546
+ * y-prosemirror and yjs. The structural walk also leaked the grandchild conf into the child
1547
+ * union; inferring from the builder keeps the nesting exact. dist-check/consumer.js pins both
1548
+ * against dist.
1516
1549
  *
1517
1550
  * @template V
1518
- * @typedef {V extends never ? never : (import('../ts.js').TypeIsAny<V,any,V extends Delta<infer Conf> ? Delta<Conf> : V>)} _SanifyDelta
1551
+ * @typedef {V extends never ? never : (import('../ts.js').TypeIsAny<V,any,V extends DeltaBuilder<infer Conf, any> ? Delta<Conf> : V>)} _SanifyDelta
1519
1552
  */
1520
1553
  /**
1521
1554
  * @template {DeltaConf} Conf
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lib0",
3
- "version": "1.0.0-rc.30",
3
+ "version": "1.0.0-rc.32",
4
4
  "description": "isomorphic utility functions",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -142,6 +142,29 @@ export const $deltaMapChangeJson = /* @__PURE__ */(() => s.$union(
142
142
  * @return {Attrs}
143
143
  */
144
144
  const _cloneAttrs = attrs => attrs == null ? attrs : { ...attrs }
145
+
146
+ /**
147
+ * A retain with neither format nor attribution — positional only, not content. `done()` trims a
148
+ * trailing run of them; fingerprint, equality and `isEmpty` ignore that run. (`format: null` is a
149
+ * clear instruction and therefore content — the same rule `done()` applies.)
150
+ *
151
+ * @param {ChildrenOpAny} op
152
+ */
153
+ const _isPlainRetain = op => $retainOp.check(op) && op.format === undefined && op.attribution === undefined
154
+
155
+ /**
156
+ * The number of content ops in a children list: its length minus the trailing plain-retain run (found
157
+ * by walking from the right). The first `n` ops from `cs.start` are the content.
158
+ *
159
+ * @param {list.List<ChildrenOpAny>} cs
160
+ * @return {number}
161
+ */
162
+ const _contentLen = cs => {
163
+ let n = cs.len
164
+ for (let end = cs.end; end !== null && _isPlainRetain(end); end = end.prev) n--
165
+ return n
166
+ }
167
+
145
168
  /**
146
169
  * Shallow per-key tri-state merge of `update` into `base` (the usual `format`-dimension semantics, also reused
147
170
  * as the inner step of {@link mergeAttr}). Per key: `undefined` skips, `null` removes, anything else sets.
@@ -308,6 +331,10 @@ const combineData = (usedData, arg, deep) => {
308
331
  return object.isEmpty(r) ? null : /** @type {{[k:string]:any}} */ (r)
309
332
  }
310
333
  /**
334
+ * Freeze a maybe-delta for a frozen clone (a scalar passes through untouched). `done()` is
335
+ * fingerprint-neutral (it only trims trailing plain retains, which are not fingerprinted), so the
336
+ * clone may carry the op's cached fingerprint over.
337
+ *
311
338
  * @template {any} MaybeDelta
312
339
  * @param {MaybeDelta} maybeDelta
313
340
  * @return {MaybeDelta}
@@ -329,6 +356,10 @@ const _markMaybeDeltaAsDone = maybeDelta => $deltaAny.check(maybeDelta) ? /** @t
329
356
  * fingerprint read (and any `diff` / equality check that relies on it) is
330
357
  * wrong. Fields covered: insert, delete, retain, format, attribution,
331
358
  * value, key.
359
+ * - **A `clone` copies `_fingerprint` when the copy is content-identical:** a full range and the
360
+ * same key. Freezing a nested value with `done()` is fingerprint-neutral (trailing plain retains
361
+ * are not fingerprinted), so nothing else can differ. The root delta returned by
362
+ * `clone`/`slice`/`cloneDeep` never inherits a fingerprint.
332
363
  *
333
364
  * @internal not part of the consumer API — a content op; build deltas via {@link create}/{@link DeltaBuilder}.
334
365
  */
@@ -419,7 +450,9 @@ export class TextOp extends list.ListNode {
419
450
  * @return {TextOp}
420
451
  */
421
452
  clone (start = 0, end = this.length, _markAsDone = true) {
422
- return new TextOp(this.insert.slice(start, end), _cloneAttrs(this.format), _cloneAttrs(this.attribution))
453
+ const cpy = new TextOp(this.insert.slice(start, end), _cloneAttrs(this.format), _cloneAttrs(this.attribution))
454
+ if (start === 0 && end === this.length) cpy._fingerprint = this._fingerprint
455
+ return cpy
423
456
  }
424
457
  }
425
458
 
@@ -541,7 +574,9 @@ export class InsertOp extends list.ListNode {
541
574
  */
542
575
  clone (start = 0, end = this.length, markAsDone = true) {
543
576
  const insert = this.insert.slice(start, end)
544
- return new InsertOp(markAsDone ? insert.map(_markMaybeDeltaAsDone) : insert, _cloneAttrs(this.format), _cloneAttrs(this.attribution))
577
+ const cpy = new InsertOp(markAsDone ? insert.map(_markMaybeDeltaAsDone) : insert, _cloneAttrs(this.format), _cloneAttrs(this.attribution))
578
+ if (start === 0 && end === this.length) cpy._fingerprint = this._fingerprint
579
+ return cpy
545
580
  }
546
581
  }
547
582
 
@@ -614,7 +649,9 @@ export class DeleteOp extends list.ListNode {
614
649
  * @return {DeleteOp}
615
650
  */
616
651
  clone (start = 0, end = this.delete, _markAsDone = true) {
617
- return new DeleteOp(end - start)
652
+ const cpy = new DeleteOp(end - start)
653
+ if (end - start === this.delete) cpy._fingerprint = this._fingerprint
654
+ return cpy
618
655
  }
619
656
  }
620
657
 
@@ -712,7 +749,9 @@ export class RetainOp extends list.ListNode {
712
749
  * @return {RetainOp}
713
750
  */
714
751
  clone (start = 0, end = this.retain, _markAsDone = true) {
715
- return new RetainOp(end - start, _cloneAttrs(this.format), _cloneAttrs(this.attribution))
752
+ const cpy = new RetainOp(end - start, _cloneAttrs(this.format), _cloneAttrs(this.attribution))
753
+ if (end - start === this.retain) cpy._fingerprint = this._fingerprint
754
+ return cpy
716
755
  }
717
756
  }
718
757
 
@@ -829,7 +868,9 @@ export class ModifyOp extends list.ListNode {
829
868
  // modify is never split (apply's `move ? op : op.clone(0, 1, keep)` evaluates the clone only when
830
869
  // `keep` is true). Kept for the uniform children-op `clone` signature.
831
870
  /* c8 ignore next */
832
- return new ModifyOp(/** @type {DTypes} */ (markAsDone ? this.value.done() : this.value), _cloneAttrs(this.format), _cloneAttrs(this.attribution))
871
+ const cpy = new ModifyOp(/** @type {DTypes} */ (markAsDone ? this.value.done() : this.value), _cloneAttrs(this.format), _cloneAttrs(this.attribution))
872
+ cpy._fingerprint = this._fingerprint
873
+ return cpy
833
874
  }
834
875
  }
835
876
 
@@ -916,10 +957,15 @@ export class SetAttrOp {
916
957
  * Full (frozen) clone. A `move` apply reuses the source op instead of cloning, so there is no
917
958
  * shared-mutable variant — see {@link DeltaBuilder#apply}.
918
959
  *
919
- * @return {SetAttrOp<V,K>}
960
+ * @template {string|number} [K2=K]
961
+ * @param {K2} [key] retarget the clone to another attribute key (a transformer projecting or
962
+ * renaming an attribute); defaults to this op's key.
963
+ * @return {SetAttrOp<V,K2>}
920
964
  */
921
- clone () {
922
- return new SetAttrOp(this.key, _markMaybeDeltaAsDone(this.value), _cloneAttrs(this.attribution))
965
+ clone (key = /** @type {any} */ (this.key)) {
966
+ const cpy = new SetAttrOp(key, _markMaybeDeltaAsDone(this.value), _cloneAttrs(this.attribution))
967
+ if (/** @type {string|number} */ (key) === this.key) cpy._fingerprint = this._fingerprint // the key is part of the fingerprint
968
+ return cpy
923
969
  }
924
970
  }
925
971
 
@@ -983,10 +1029,14 @@ export class DeleteAttrOp {
983
1029
  /**
984
1030
  * Full (frozen) clone; a `move` apply reuses the source op instead — see {@link DeltaBuilder#apply}.
985
1031
  *
986
- * @return {DeleteAttrOp<V,K>}
1032
+ * @template {string|number} [K2=K]
1033
+ * @param {K2} [key] retarget the clone to another attribute key; defaults to this op's key.
1034
+ * @return {DeleteAttrOp<V,K2>}
987
1035
  */
988
- clone () {
989
- return new DeleteAttrOp(this.key, _cloneAttrs(this.attribution))
1036
+ clone (key = /** @type {any} */ (this.key)) {
1037
+ const cpy = new DeleteAttrOp(key, _cloneAttrs(this.attribution))
1038
+ if (/** @type {string|number} */ (key) === this.key) cpy._fingerprint = this._fingerprint // the key is part of the fingerprint
1039
+ return cpy
990
1040
  }
991
1041
  }
992
1042
 
@@ -1069,10 +1119,14 @@ export class ModifyAttrOp {
1069
1119
  /**
1070
1120
  * Full (frozen) clone; a `move` apply reuses the source op instead — see {@link DeltaBuilder#apply}.
1071
1121
  *
1072
- * @return {ModifyAttrOp<Modifier,K>}
1122
+ * @template {string|number} [K2=K]
1123
+ * @param {K2} [key] retarget the clone to another attribute key; defaults to this op's key.
1124
+ * @return {ModifyAttrOp<Modifier,K2>}
1073
1125
  */
1074
- clone () {
1075
- return new ModifyAttrOp(this.key, /** @type {Modifier} */ (this.value.done()), _cloneAttrs(this.attribution))
1126
+ clone (key = /** @type {any} */ (this.key)) {
1127
+ const cpy = new ModifyAttrOp(key, /** @type {Modifier} */ (this.value.done()), _cloneAttrs(this.attribution))
1128
+ if (/** @type {string|number} */ (key) === this.key) cpy._fingerprint = this._fingerprint // the key is part of the fingerprint
1129
+ return cpy
1076
1130
  }
1077
1131
  }
1078
1132
 
@@ -1306,10 +1360,21 @@ export const createMark = (key, id, assoc, attrs, transient) => new Mark(key, id
1306
1360
  */
1307
1361
 
1308
1362
  /**
1309
- * Transform Delta(Builder) to a normal delta.
1363
+ * Transform Delta(Builder) to a normal delta: a `DeltaBuilder<Conf>` becomes `Delta<Conf>`, anything
1364
+ * else passes through.
1365
+ *
1366
+ * Infers the conf from `DeltaBuilder` on purpose, not from `Delta<infer Conf>`. lib0's own source
1367
+ * check never had a problem with the latter, but a consumer type-checks the emitted declaration
1368
+ * files, and there matching a builder against `Delta<infer Conf>` is structural: TypeScript walks
1369
+ * `Delta`'s members and recurses through {@link DeltaConfGetChildren}'s recursive branch until its
1370
+ * instantiation-depth limit, so every nested condensed builder (for example
1371
+ * `create().insert([create('c', {}, [create('p', {}, 'x')])])`) failed with TS2589 in
1372
+ * y-prosemirror and yjs. The structural walk also leaked the grandchild conf into the child
1373
+ * union; inferring from the builder keeps the nesting exact. dist-check/consumer.js pins both
1374
+ * against dist.
1310
1375
  *
1311
1376
  * @template V
1312
- * @typedef {V extends never ? never : (import('../ts.js').TypeIsAny<V,any,V extends Delta<infer Conf> ? Delta<Conf> : V>)} _SanifyDelta
1377
+ * @typedef {V extends never ? never : (import('../ts.js').TypeIsAny<V,any,V extends DeltaBuilder<infer Conf, any> ? Delta<Conf> : V>)} _SanifyDelta
1313
1378
  */
1314
1379
 
1315
1380
  /**
@@ -1443,8 +1508,13 @@ export class Delta extends DeltaData {
1443
1508
  for (const key of keys) {
1444
1509
  encoding.writeVarString(encoder, /** @type {any} */ (this.attrs[/** @type {keyof typeof this.attrs} */ (key)]).fingerprint)
1445
1510
  }
1446
- encoding.writeVarUint(encoder, this.children.len)
1447
- for (const child of this.children) {
1511
+ // trailing plain retains are not content: `done()` trims them, and the fingerprint must not
1512
+ // change when it does — so only the children up to the last content op are fingerprinted
1513
+ const cs = this.children
1514
+ let n = _contentLen(cs)
1515
+ encoding.writeVarUint(encoder, n)
1516
+ // non-null for the first `n` ops by construction (see _contentLen)
1517
+ for (let child = /** @type {ChildrenOpAny} */ (cs.start); n > 0; n--, child = /** @type {ChildrenOpAny} */ (child.next)) {
1448
1518
  encoding.writeVarString(encoder, child.fingerprint)
1449
1519
  }
1450
1520
  }))))
@@ -1455,7 +1525,8 @@ export class Delta extends DeltaData {
1455
1525
  }
1456
1526
 
1457
1527
  isEmpty () {
1458
- return object.isEmpty(this.attrs) && list.isEmpty(this.children) && (this.marks === null || this.marks.size === 0) && (this.deleteMarks === null || this.deleteMarks.size === 0)
1528
+ // a children list holding only plain retains is empty (positional only — see _isPlainRetain)
1529
+ return object.isEmpty(this.attrs) && _contentLen(this.children) === 0 && (this.marks === null || this.marks.size === 0) && (this.deleteMarks === null || this.deleteMarks.size === 0)
1459
1530
  }
1460
1531
 
1461
1532
  /**
@@ -1500,10 +1571,20 @@ export class Delta extends DeltaData {
1500
1571
  * @return {boolean}
1501
1572
  */
1502
1573
  [equalityTrait.EqualityTraitSymbol] (other) {
1503
- // @todo it is only necessary to compare finrerprints OR do a deep equality check (remove
1504
- // childCnt as well)
1505
1574
  // marks are local/ephemeral cursor state and intentionally NOT part of document identity
1506
- return this.name === other.name && fun.equalityDeep(this.attrs, other.attrs) && fun.equalityDeep(this.children, other.children) && this.childCnt === other.childCnt
1575
+ if (this.name !== other.name || !fun.equalityDeep(this.attrs, other.attrs)) return false
1576
+ // only the content ops count (a trailing plain-retain run is not content — see _isPlainRetain);
1577
+ // comparing the counts first also makes two retain-only lists equal. `childCnt` (Σ op.length)
1578
+ // needs no separate check: every content op's equality covers its length-bearing field.
1579
+ const n = _contentLen(this.children)
1580
+ if (n !== _contentLen(other.children)) return false
1581
+ // non-null for the first `n` ops by construction (see _contentLen)
1582
+ let a = /** @type {ChildrenOpAny} */ (this.children.start)
1583
+ let b = /** @type {ChildrenOpAny} */ (other.children.start)
1584
+ for (let i = 0; i < n; i++, a = /** @type {ChildrenOpAny} */ (a.next), b = /** @type {ChildrenOpAny} */ (b.next)) {
1585
+ if (!fun.equalityDeep(a, b)) return false
1586
+ }
1587
+ return true
1507
1588
  }
1508
1589
 
1509
1590
  // toString () {
@@ -1531,11 +1612,12 @@ export class Delta extends DeltaData {
1531
1612
  done (markAsDone = true) {
1532
1613
  if (!this.isDone) {
1533
1614
  this.isDone = markAsDone
1615
+ // trim the trailing plain-retain run — fingerprint-neutral, as fingerprint/equality never counted
1616
+ // it (see _contentLen); any other cleanup added here must null `_fingerprint`
1534
1617
  const cs = this.children
1535
- for (let end = cs.end; end !== null && $retainOp.check(end) && end.format === undefined && end.attribution === undefined; end = cs.end) {
1618
+ for (let end = cs.end; end !== null && _isPlainRetain(end); end = cs.end) {
1536
1619
  this.childCnt -= end.length
1537
1620
  list.popEnd(cs)
1538
- this._fingerprint = null
1539
1621
  }
1540
1622
  }
1541
1623
  return this
@@ -1631,22 +1713,48 @@ export const clone = d => /** @type {any} */ (slice(d, 0, d.childCnt))
1631
1713
  * @param {any} v
1632
1714
  * @return {any}
1633
1715
  */
1634
- const _cloneMaybeDeltaDeep = v => $deltaAny.check(v) ? cloneDeep(v) : v
1716
+ const _cloneMaybeDeltaDeep = v => $deltaAny.check(v) ? _cloneDeepNested(v) : v
1717
+
1718
+ /**
1719
+ * Carry `src`'s cached fingerprint over to `cpy`, a content-identical copy of it. Only valid when
1720
+ * nothing about the copy differs from the source in fingerprinted fields (a full-range clone, the same
1721
+ * key, nested values cloned rather than `done()`-trimmed).
1722
+ *
1723
+ * @template {{ _fingerprint: string|null }} T
1724
+ * @param {{ _fingerprint: string|null }} src
1725
+ * @param {T} cpy
1726
+ * @return {T}
1727
+ */
1728
+ const _copyFp = (src, cpy) => {
1729
+ cpy._fingerprint = src._fingerprint
1730
+ return cpy
1731
+ }
1732
+
1733
+ /**
1734
+ * {@link cloneDeep} for a *nested* delta: content-identical, so the cached fingerprint rides along
1735
+ * (only the root returned by the exported `cloneDeep` starts without one).
1736
+ *
1737
+ * @template {DeltaConf} Conf
1738
+ * @param {Delta<Conf>} d
1739
+ * @return {DeltaBuilder<Conf>}
1740
+ */
1741
+ const _cloneDeepNested = d => _copyFp(d, cloneDeep(d))
1635
1742
 
1636
1743
  /**
1637
1744
  * Deep-clone one content (child) op for {@link cloneDeep}: a fresh op whose nested deltas (an insert's
1638
1745
  * delta content, a modify's value) are themselves deep-cloned. `format`/`attribution` objects are
1639
1746
  * retained (shared) — they are always copied before being mutated (see {@link cloneDeep}). Ops without a
1640
- * nested delta (`text`/`retain`/`delete`) use `op.clone()`.
1747
+ * nested delta (`text`/`retain`/`delete`) use `op.clone()`. Every copy is content-identical, so the
1748
+ * cached fingerprint is carried over.
1641
1749
  *
1642
1750
  * @param {ChildrenOpAny} op
1643
1751
  * @return {ChildrenOpAny}
1644
1752
  */
1645
1753
  const _cloneChildOpDeep = op =>
1646
1754
  $insertOp.check(op)
1647
- ? new InsertOp(op.insert.map(_cloneMaybeDeltaDeep), op.format, op.attribution)
1755
+ ? _copyFp(op, new InsertOp(op.insert.map(_cloneMaybeDeltaDeep), op.format, op.attribution))
1648
1756
  : ($modifyOp.check(op)
1649
- ? new ModifyOp(cloneDeep(op.value), op.format, op.attribution)
1757
+ ? _copyFp(op, new ModifyOp(_cloneDeepNested(op.value), op.format, op.attribution))
1650
1758
  : op.clone())
1651
1759
 
1652
1760
  /**
@@ -1659,17 +1767,18 @@ const _cloneChildOpDeep = op =>
1659
1767
  */
1660
1768
  const _cloneAttrOpDeep = op =>
1661
1769
  $setAttrOp.check(op)
1662
- ? new SetAttrOp(op.key, _cloneMaybeDeltaDeep(op.value), op.attribution)
1770
+ ? _copyFp(op, new SetAttrOp(op.key, _cloneMaybeDeltaDeep(op.value), op.attribution))
1663
1771
  : ($modifyAttrOp.check(op)
1664
- ? new ModifyAttrOp(op.key, cloneDeep(op.value), op.attribution)
1772
+ ? _copyFp(op, new ModifyAttrOp(op.key, _cloneDeepNested(op.value), op.attribution))
1665
1773
  // the only remaining attr op is a deleteAttr
1666
- : new DeleteAttrOp(op.key, op.attribution))
1774
+ : _copyFp(op, new DeleteAttrOp(op.key, op.attribution)))
1667
1775
 
1668
1776
  /**
1669
1777
  * A **deep** clone of `d`: like {@link clone}, but every nested delta — an insert's delta content, a
1670
1778
  * `modify`/`modifyAttr` value, a delta-valued attribute — is itself recursively cloned into a fresh,
1671
1779
  * **mutable** node, instead of being frozen (`done`) and shared as {@link clone} does. The result and
1672
- * its whole subtree are therefore independently editable.
1780
+ * its whole subtree are therefore independently editable. Cached fingerprints of the ops and nested
1781
+ * deltas ride along (they are content-identical); only the returned root starts without one.
1673
1782
  *
1674
1783
  * ## What is cloned, and when to reach for this
1675
1784
  *
@@ -1773,7 +1882,7 @@ const modValue = op => {
1773
1882
  * These are the ONLY sanctioned way to structurally edit `d.children` in place. Each keeps the cached
1774
1883
  * invariant `d.childCnt === Σ op.length` correct and resets the op's `_fingerprint`. Never mutate
1775
1884
  * `op.retain` / `op.delete` / `op.insert`, call `list.*`, or touch `d.childCnt` directly from outside
1776
- * delta.js — go through these so the count (which `EqualityTraitSymbol` compares) never drifts.
1885
+ * delta.js — go through these so the count (which `slice`/`apply` position by) never drifts.
1777
1886
  *
1778
1887
  * Cursor pattern (used by {@link DeltaBuilder#apply}, {@link DeltaBuilder#rebase}, and
1779
1888
  * `transformer/conform.js`): walk the target with `(op, offset)`; {@link _splitChildAt} before inserting
@@ -17,11 +17,9 @@ import { Transformer, Template, createTransformResult, attrsShapeOf } from './co
17
17
  const attrTransformHelper = (outDelta, from, to, inDelta) => {
18
18
  const attrOp = inDelta.attrs[from]
19
19
  if (attrOp != null) {
20
- const c = attrOp.clone()
21
- // reason: retarget the cloned attr op to the dynamic key `to`; `key` is readonly and `attrs` is
22
- // a mapped type over fixed conf keys, so neither write is expressible in the JSDoc types.
23
- // @ts-ignore
24
- c.key = to
20
+ // a clone retargeted to `to` (the clone skips the cached fingerprint, which covers the key)
21
+ const c = attrOp.clone(to)
22
+ // `attrs` is a mapped type over fixed conf keys, so the dynamic-key write needs the cast
25
23
  const oattrs = /** @type {any} */ (outDelta.attrs)
26
24
  oattrs[to] = c
27
25
  }
@@ -21,12 +21,11 @@ const renameDeltaAttrs = (d, renames, revRenames) => {
21
21
  const r = renames[key]
22
22
  const rv = revRenames[key]
23
23
  if (r != null) {
24
+ // a clone retargeted to `r` (the clone skips the cached fingerprint, which covers the key)
24
25
  // @ts-ignore
25
- forwardTransform.attrs[r] = attr
26
+ forwardTransform.attrs[r] = attr.clone(r)
26
27
  // delete original
27
28
  delete forwardTransform.attrs[key]
28
- // @ts-ignore
29
- attr.key = r
30
29
  } else if (rv != null) {
31
30
  // used in a rename, delete original
32
31
  delete forwardTransform.attrs[key]