lib0 1.0.0-rc.30 → 1.0.0-rc.31

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>>;
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.31",
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
 
@@ -1443,8 +1497,13 @@ export class Delta extends DeltaData {
1443
1497
  for (const key of keys) {
1444
1498
  encoding.writeVarString(encoder, /** @type {any} */ (this.attrs[/** @type {keyof typeof this.attrs} */ (key)]).fingerprint)
1445
1499
  }
1446
- encoding.writeVarUint(encoder, this.children.len)
1447
- for (const child of this.children) {
1500
+ // trailing plain retains are not content: `done()` trims them, and the fingerprint must not
1501
+ // change when it does — so only the children up to the last content op are fingerprinted
1502
+ const cs = this.children
1503
+ let n = _contentLen(cs)
1504
+ encoding.writeVarUint(encoder, n)
1505
+ // non-null for the first `n` ops by construction (see _contentLen)
1506
+ for (let child = /** @type {ChildrenOpAny} */ (cs.start); n > 0; n--, child = /** @type {ChildrenOpAny} */ (child.next)) {
1448
1507
  encoding.writeVarString(encoder, child.fingerprint)
1449
1508
  }
1450
1509
  }))))
@@ -1455,7 +1514,8 @@ export class Delta extends DeltaData {
1455
1514
  }
1456
1515
 
1457
1516
  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)
1517
+ // a children list holding only plain retains is empty (positional only — see _isPlainRetain)
1518
+ return object.isEmpty(this.attrs) && _contentLen(this.children) === 0 && (this.marks === null || this.marks.size === 0) && (this.deleteMarks === null || this.deleteMarks.size === 0)
1459
1519
  }
1460
1520
 
1461
1521
  /**
@@ -1500,10 +1560,20 @@ export class Delta extends DeltaData {
1500
1560
  * @return {boolean}
1501
1561
  */
1502
1562
  [equalityTrait.EqualityTraitSymbol] (other) {
1503
- // @todo it is only necessary to compare finrerprints OR do a deep equality check (remove
1504
- // childCnt as well)
1505
1563
  // 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
1564
+ if (this.name !== other.name || !fun.equalityDeep(this.attrs, other.attrs)) return false
1565
+ // only the content ops count (a trailing plain-retain run is not content — see _isPlainRetain);
1566
+ // comparing the counts first also makes two retain-only lists equal. `childCnt` (Σ op.length)
1567
+ // needs no separate check: every content op's equality covers its length-bearing field.
1568
+ const n = _contentLen(this.children)
1569
+ if (n !== _contentLen(other.children)) return false
1570
+ // non-null for the first `n` ops by construction (see _contentLen)
1571
+ let a = /** @type {ChildrenOpAny} */ (this.children.start)
1572
+ let b = /** @type {ChildrenOpAny} */ (other.children.start)
1573
+ for (let i = 0; i < n; i++, a = /** @type {ChildrenOpAny} */ (a.next), b = /** @type {ChildrenOpAny} */ (b.next)) {
1574
+ if (!fun.equalityDeep(a, b)) return false
1575
+ }
1576
+ return true
1507
1577
  }
1508
1578
 
1509
1579
  // toString () {
@@ -1531,11 +1601,12 @@ export class Delta extends DeltaData {
1531
1601
  done (markAsDone = true) {
1532
1602
  if (!this.isDone) {
1533
1603
  this.isDone = markAsDone
1604
+ // trim the trailing plain-retain run — fingerprint-neutral, as fingerprint/equality never counted
1605
+ // it (see _contentLen); any other cleanup added here must null `_fingerprint`
1534
1606
  const cs = this.children
1535
- for (let end = cs.end; end !== null && $retainOp.check(end) && end.format === undefined && end.attribution === undefined; end = cs.end) {
1607
+ for (let end = cs.end; end !== null && _isPlainRetain(end); end = cs.end) {
1536
1608
  this.childCnt -= end.length
1537
1609
  list.popEnd(cs)
1538
- this._fingerprint = null
1539
1610
  }
1540
1611
  }
1541
1612
  return this
@@ -1631,22 +1702,48 @@ export const clone = d => /** @type {any} */ (slice(d, 0, d.childCnt))
1631
1702
  * @param {any} v
1632
1703
  * @return {any}
1633
1704
  */
1634
- const _cloneMaybeDeltaDeep = v => $deltaAny.check(v) ? cloneDeep(v) : v
1705
+ const _cloneMaybeDeltaDeep = v => $deltaAny.check(v) ? _cloneDeepNested(v) : v
1706
+
1707
+ /**
1708
+ * Carry `src`'s cached fingerprint over to `cpy`, a content-identical copy of it. Only valid when
1709
+ * nothing about the copy differs from the source in fingerprinted fields (a full-range clone, the same
1710
+ * key, nested values cloned rather than `done()`-trimmed).
1711
+ *
1712
+ * @template {{ _fingerprint: string|null }} T
1713
+ * @param {{ _fingerprint: string|null }} src
1714
+ * @param {T} cpy
1715
+ * @return {T}
1716
+ */
1717
+ const _copyFp = (src, cpy) => {
1718
+ cpy._fingerprint = src._fingerprint
1719
+ return cpy
1720
+ }
1721
+
1722
+ /**
1723
+ * {@link cloneDeep} for a *nested* delta: content-identical, so the cached fingerprint rides along
1724
+ * (only the root returned by the exported `cloneDeep` starts without one).
1725
+ *
1726
+ * @template {DeltaConf} Conf
1727
+ * @param {Delta<Conf>} d
1728
+ * @return {DeltaBuilder<Conf>}
1729
+ */
1730
+ const _cloneDeepNested = d => _copyFp(d, cloneDeep(d))
1635
1731
 
1636
1732
  /**
1637
1733
  * Deep-clone one content (child) op for {@link cloneDeep}: a fresh op whose nested deltas (an insert's
1638
1734
  * delta content, a modify's value) are themselves deep-cloned. `format`/`attribution` objects are
1639
1735
  * retained (shared) — they are always copied before being mutated (see {@link cloneDeep}). Ops without a
1640
- * nested delta (`text`/`retain`/`delete`) use `op.clone()`.
1736
+ * nested delta (`text`/`retain`/`delete`) use `op.clone()`. Every copy is content-identical, so the
1737
+ * cached fingerprint is carried over.
1641
1738
  *
1642
1739
  * @param {ChildrenOpAny} op
1643
1740
  * @return {ChildrenOpAny}
1644
1741
  */
1645
1742
  const _cloneChildOpDeep = op =>
1646
1743
  $insertOp.check(op)
1647
- ? new InsertOp(op.insert.map(_cloneMaybeDeltaDeep), op.format, op.attribution)
1744
+ ? _copyFp(op, new InsertOp(op.insert.map(_cloneMaybeDeltaDeep), op.format, op.attribution))
1648
1745
  : ($modifyOp.check(op)
1649
- ? new ModifyOp(cloneDeep(op.value), op.format, op.attribution)
1746
+ ? _copyFp(op, new ModifyOp(_cloneDeepNested(op.value), op.format, op.attribution))
1650
1747
  : op.clone())
1651
1748
 
1652
1749
  /**
@@ -1659,17 +1756,18 @@ const _cloneChildOpDeep = op =>
1659
1756
  */
1660
1757
  const _cloneAttrOpDeep = op =>
1661
1758
  $setAttrOp.check(op)
1662
- ? new SetAttrOp(op.key, _cloneMaybeDeltaDeep(op.value), op.attribution)
1759
+ ? _copyFp(op, new SetAttrOp(op.key, _cloneMaybeDeltaDeep(op.value), op.attribution))
1663
1760
  : ($modifyAttrOp.check(op)
1664
- ? new ModifyAttrOp(op.key, cloneDeep(op.value), op.attribution)
1761
+ ? _copyFp(op, new ModifyAttrOp(op.key, _cloneDeepNested(op.value), op.attribution))
1665
1762
  // the only remaining attr op is a deleteAttr
1666
- : new DeleteAttrOp(op.key, op.attribution))
1763
+ : _copyFp(op, new DeleteAttrOp(op.key, op.attribution)))
1667
1764
 
1668
1765
  /**
1669
1766
  * A **deep** clone of `d`: like {@link clone}, but every nested delta — an insert's delta content, a
1670
1767
  * `modify`/`modifyAttr` value, a delta-valued attribute — is itself recursively cloned into a fresh,
1671
1768
  * **mutable** node, instead of being frozen (`done`) and shared as {@link clone} does. The result and
1672
- * its whole subtree are therefore independently editable.
1769
+ * its whole subtree are therefore independently editable. Cached fingerprints of the ops and nested
1770
+ * deltas ride along (they are content-identical); only the returned root starts without one.
1673
1771
  *
1674
1772
  * ## What is cloned, and when to reach for this
1675
1773
  *
@@ -1773,7 +1871,7 @@ const modValue = op => {
1773
1871
  * These are the ONLY sanctioned way to structurally edit `d.children` in place. Each keeps the cached
1774
1872
  * invariant `d.childCnt === Σ op.length` correct and resets the op's `_fingerprint`. Never mutate
1775
1873
  * `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.
1874
+ * delta.js — go through these so the count (which `slice`/`apply` position by) never drifts.
1777
1875
  *
1778
1876
  * Cursor pattern (used by {@link DeltaBuilder#apply}, {@link DeltaBuilder#rebase}, and
1779
1877
  * `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]