lib0 1.0.0-rc.19 → 1.0.0-rc.20

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.
@@ -631,6 +631,7 @@ export class Delta<Conf extends DeltaConf = {}> extends DeltaData<DeltaConfGetNa
631
631
  }
632
632
  export function slice<Conf_1 extends DeltaConf>(d: Delta<Conf_1>, start?: number, end?: number, currNode?: ChildrenOpAny | null): DeltaBuilder<Conf_1>;
633
633
  export function clone<D extends DeltaAny>(d: D): D extends Delta<infer Conf_1> ? DeltaBuilder<Conf_1> : never;
634
+ export function cloneDeep<Conf_1 extends DeltaConf>(d: Delta<Conf_1>): DeltaBuilder<Conf_1>;
634
635
  export function cloneShallow<D extends DeltaAny>(d: D): D extends Delta<infer Conf_1> ? DeltaBuilder<Conf_1> : never;
635
636
  export function _mergeChildWithPrev(d: DeltaBuilderAny, op: InsertOp<any> | RetainOp | DeleteOp<any> | TextOp | ModifyOp<any>): boolean;
636
637
  export function _splitChildAt(d: DeltaBuilderAny, op: ChildrenOpAny, offset: number): ChildrenOpAny;
@@ -1244,6 +1245,15 @@ export type DiffOptions = {
1244
1245
  * (`(d1, d2) => d1.name === d2.name`). Called as `compare(fromNode, toNode)`.
1245
1246
  */
1246
1247
  compare?: ((d1: DeltaAny, d2: DeltaAny) => boolean) | undefined;
1248
+ /**
1249
+ * By default `diff` does **not** produce a fully fresh delta: wherever
1250
+ * it emits content from `d2` — a wholesale-inserted child node, or a delta-valued attribute — it *shares*
1251
+ * that nested delta by reference, so the result aliases `d2`'s subtrees. Set `true` to
1252
+ * {@link cloneDeep deep-clone} every such delta (children **and** attributes) as it is included in the
1253
+ * output, yielding a result that shares no structure with `d2` (and is safe to hand to something that
1254
+ * manipulates deltas in place, e.g. a transformer).
1255
+ */
1256
+ clone?: boolean | undefined;
1247
1257
  };
1248
1258
  import * as s from '../schema.js';
1249
1259
  import * as list from '../list.js';
@@ -1,32 +1,32 @@
1
- export function deltaRDT<D extends delta.DeltaAny>($delta: Schema<D>): DeltaRDT<D>;
1
+ export function deltaRDT<Conf extends delta.DeltaConf>($delta: Schema<delta.Delta<Conf>>): DeltaRDT<Conf>;
2
2
  export type Schema<T> = import("../../schema.js").Schema<T>;
3
- export type RDT<D extends delta.DeltaAny> = import("../rdt.js").RDT<D>;
3
+ export type RDT<Conf extends delta.DeltaConf> = import("../rdt.js").RDT<Conf>;
4
4
  import * as delta from '../delta.js';
5
5
  /**
6
6
  * @template T
7
7
  * @typedef {import('../../schema.js').Schema<T>} Schema
8
8
  */
9
9
  /**
10
- * @template {delta.DeltaAny} D
11
- * @typedef {import('../rdt.js').RDT<D>} RDT
10
+ * @template {delta.DeltaConf} Conf
11
+ * @typedef {import('../rdt.js').RDT<Conf>} RDT
12
12
  */
13
13
  /**
14
14
  * An in-memory RDT whose state is the accumulated delta. Calling `applyDelta` merges the incoming delta
15
15
  * into `state` (via {@link delta.DeltaBuilder#apply}) and re-emits it as a `'delta'`.
16
16
  *
17
- * @template {delta.DeltaAny} D
18
- * @implements {RDT<D>}
19
- * @extends {ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: DeltaRDT<D>) => void }>}
17
+ * @template {delta.DeltaConf} Conf
18
+ * @implements {RDT<Conf>}
19
+ * @extends {ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void, destroy: (rdt: DeltaRDT<Conf>) => void }>}
20
20
  */
21
- declare class DeltaRDT<D extends delta.DeltaAny> extends ObservableV2<{
22
- delta: (delta: D) => void;
23
- destroy: (rdt: DeltaRDT<D>) => void;
24
- }> implements RDT<D> {
21
+ declare class DeltaRDT<Conf extends delta.DeltaConf> extends ObservableV2<{
22
+ delta: (delta: delta.Delta<Conf>, origin: any) => void;
23
+ destroy: (rdt: DeltaRDT<Conf>) => void;
24
+ }> implements RDT<Conf> {
25
25
  /**
26
- * @param {Schema<D>} $delta schema of the deltas this RDT produces
26
+ * @param {Schema<delta.Delta<Conf>>} $delta schema of the deltas this RDT produces
27
27
  */
28
- constructor($delta: Schema<D>);
29
- $delta: import("../../schema.js").Schema<D>;
28
+ constructor($delta: Schema<delta.Delta<Conf>>);
29
+ $delta: import("../../schema.js").Schema<delta.Delta<Conf>>;
30
30
  /**
31
31
  * @type {delta.DeltaBuilderAny?}
32
32
  */
@@ -36,17 +36,20 @@ declare class DeltaRDT<D extends delta.DeltaAny> extends ObservableV2<{
36
36
  * Merge an incoming delta into the current state and notify observers. Always returns `null`: this
37
37
  * RDT accepts every valid delta unchanged, so it never produces a fix.
38
38
  *
39
- * @param {D} d
39
+ * @param {delta.Delta<Conf>} d
40
+ * @param {any} [origin] who produced the change; forwarded verbatim on the emitted `'delta'` event so
41
+ * listeners can recognise (and skip) changes they made themselves — see {@link RDT} “Origins”. Defaults
42
+ * to `null` (an anonymous/local change).
40
43
  * @return {null}
41
44
  */
42
- applyDelta(d: D): null;
45
+ applyDelta(d: delta.Delta<Conf>, origin?: any): null;
43
46
  /**
44
47
  * The current state as a delta: the accumulated `state`, or an empty delta when nothing has been
45
48
  * applied yet.
46
49
  *
47
- * @return {D}
50
+ * @return {delta.Delta<Conf>}
48
51
  */
49
- get delta(): D;
52
+ get delta(): delta.Delta<Conf>;
50
53
  }
51
54
  import { ObservableV2 } from '../../observable.js';
52
55
  import * as mux from '../../mutex.js';
@@ -10,42 +10,50 @@ export const $domDelta: s.Schema<delta.Delta<{
10
10
  text: true;
11
11
  recursiveChildren: true;
12
12
  }>>;
13
- export function domRDT(dom: Element): DomRDT<DomDelta>;
13
+ export function domRDT(dom: Element): DomRDT;
14
14
  export type Schema<T> = import("../../schema.js").Schema<T>;
15
- export type RDT<D extends delta.DeltaAny> = import("../rdt.js").RDT<D>;
16
- export type DomDelta = delta.Delta<{
15
+ export type RDT<Conf extends delta.DeltaConf> = import("../rdt.js").RDT<Conf>;
16
+ /**
17
+ * The {@link delta.DeltaConf} of the deltas a {@link DomRDT} produces/consumes (see {@link $domDelta}).
18
+ */
19
+ export type DomConf = {
17
20
  name: string;
18
21
  attrs: {
19
22
  [key: string]: string;
20
23
  };
21
24
  text: true;
22
25
  recursiveChildren: true;
23
- }>;
26
+ };
27
+ export type DomDelta = delta.Delta<DomConf>;
24
28
  import * as delta from '../delta.js';
25
29
  import * as s from '../../schema.js';
26
30
  /**
27
- * @typedef {delta.Delta<{ name: string, attrs: { [key:string]: string }, text: true, recursiveChildren: true }>} DomDelta
31
+ * The {@link delta.DeltaConf} of the deltas a {@link DomRDT} produces/consumes (see {@link $domDelta}).
32
+ *
33
+ * @typedef {{ name: string, attrs: { [key:string]: string }, text: true, recursiveChildren: true }} DomConf
34
+ */
35
+ /**
36
+ * @typedef {delta.Delta<DomConf>} DomDelta
28
37
  */
29
38
  /**
30
39
  * An RDT backed by a live DOM subtree. DOM mutations observed via `MutationObserver` are diffed
31
40
  * against the last-known state and emitted as deltas; incoming deltas are applied back onto the DOM.
32
41
  *
33
- * @template {DomDelta} [D=DomDelta]
34
- * @implements {RDT<D>}
35
- * @extends {ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: DomRDT<D>) => void }>}
42
+ * @implements {RDT<DomConf>}
43
+ * @extends {ObservableV2<{ delta: (delta: delta.Delta<DomConf>, origin: any) => void, destroy: (rdt: DomRDT) => void }>}
36
44
  */
37
- declare class DomRDT<D extends DomDelta = DomDelta> extends ObservableV2<{
38
- delta: (delta: D) => void;
39
- destroy: (rdt: DomRDT<D>) => void;
40
- }> implements RDT<D> {
45
+ declare class DomRDT extends ObservableV2<{
46
+ delta: (delta: delta.Delta<DomConf>, origin: any) => void;
47
+ destroy: (rdt: DomRDT) => void;
48
+ }> implements RDT<DomConf> {
41
49
  /**
42
50
  * @param {Element} observedNode
43
51
  */
44
52
  constructor(observedNode: Element);
45
53
  /**
46
- * @type {Schema<D>}
54
+ * @type {Schema<DomDelta>}
47
55
  */
48
- $delta: Schema<D>;
56
+ $delta: Schema<DomDelta>;
49
57
  observedNode: Element;
50
58
  /**
51
59
  * Last-known DOM state. The observe path diffs the live DOM against this to compute the change to
@@ -74,16 +82,20 @@ declare class DomRDT<D extends DomDelta = DomDelta> extends ObservableV2<{
74
82
  * `d` rebased onto `b` is applied to the DOM, and `b` rebased onto `d` is returned as the fix so the
75
83
  * binding pipes it back through the transformer to the other side.
76
84
  *
77
- * @param {D} d
78
- * @return {D | null} the rebased local change (`b`), or `null` when there were no concurrent edits
85
+ * @param {delta.Delta<DomConf>} d
86
+ * @param {any} [origin] who produced `d`; forwarded verbatim on the emitted `'delta'` event so
87
+ * listeners can recognise (and skip) their own changes — see {@link RDT} “Origins”. Defaults to `null`
88
+ * (an anonymous/local change).
89
+ * @return {delta.DeltaBuilder<DomConf> | null} the rebased local change (`b`), or `null` when there
90
+ * were no concurrent edits
79
91
  */
80
- applyDelta(d: D): D | null;
92
+ applyDelta(d: delta.Delta<DomConf>, origin?: any): delta.DeltaBuilder<DomConf> | null;
81
93
  /**
82
94
  * The current state as a delta: an "insert everything" delta describing the observed subtree.
83
95
  *
84
- * @return {D}
96
+ * @return {delta.Delta<DomConf>}
85
97
  */
86
- get delta(): D;
98
+ get delta(): delta.Delta<DomConf>;
87
99
  }
88
100
  import { ObservableV2 } from '../../observable.js';
89
101
  export {};
@@ -11,17 +11,45 @@ export { deltaRDT } from "./rdt/delta.js";
11
11
  * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign
12
12
  * deltas via `applyDelta`.
13
13
  *
14
- * `applyDelta(d)` applies `d` and then, if the change would violate the RDT's own invariants, applies
15
- * a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns that fix
16
- * — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding} maps it
17
- * onto the other side just like any other change. `applyDelta` also emits the *effective* change
14
+ * `applyDelta(d, origin)` applies `d` and then, if the change would violate the RDT's own invariants,
15
+ * applies a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns
16
+ * that fix — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding}
17
+ * maps it onto the other side just like any other change. `applyDelta` also emits the *effective* change
18
18
  * (`d` together with the fix) on the `'delta'` channel.
19
19
  *
20
- * @template {import('./delta.js').DeltaAny} D
21
- * @typedef {import('../observable.js').ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: RDT<D>) => void }> & {
22
- * $delta: Schema<D>,
23
- * delta: D,
24
- * applyDelta: (delta: D) => D | null,
20
+ * ## Origins
21
+ *
22
+ * Every `'delta'` event carries a second argument, `origin` — an opaque `any` value (modelled after
23
+ * {@link https://docs.yjs.dev/api/transactions Yjs transaction origins}) that identifies **where the
24
+ * change came from**. Whoever produces the change sets it, and it is usually the producing instance
25
+ * itself: a communication provider, an editor binding, or the {@link Binding} that mapped the change
26
+ * over from the other side. A change an RDT observes locally (e.g. a `MutationObserver` firing on a DOM
27
+ * edit) uses that RDT itself as the origin.
28
+ *
29
+ * Origins let a listener tell **its own** changes apart from foreign ones so it can skip the ones it
30
+ * already knows about — a change it produced is not looped back to where it came from. The canonical use
31
+ * is a network provider: it tags every remote update it applies with itself (`rdt.applyDelta(update,
32
+ * this)`) and then ignores `'delta'` events whose `origin === this`, so it never re-broadcasts an update
33
+ * it just received. The `origin` argument to `applyDelta` is optional and defaults to `null` (an
34
+ * anonymous/local change); the emitted event always carries whatever value was supplied.
35
+ *
36
+ * ## `Delta` vs `DeltaBuilder`
37
+ *
38
+ * The interface is parameterized by a {@link import('./delta.js').DeltaConf DeltaConf} (like a
39
+ * {@link import('./transformer.js').Transformer transformer}). Almost everything it exposes is the
40
+ * *read* form — {@link import('./delta.js').Delta Delta} — because those values are **shared** and must
41
+ * not be mutated in place: `$delta` is the delta schema, the `delta` getter returns a state snapshot,
42
+ * `applyDelta` reads a change into the RDT, and the `'delta'` event payload is broadcast to every
43
+ * listener. The one *owned, mutable* form — {@link import('./delta.js').DeltaBuilder DeltaBuilder} — is
44
+ * the fix `applyDelta` returns to the {@link Binding}. The binding never mutates a shared read delta: it
45
+ * {@link import('./delta.js').cloneDeep deep-clones} every change into a private builder before the
46
+ * transformer rebases it (transformers manipulate deltas in place — see {@link Binding}).
47
+ *
48
+ * @template {import('./delta.js').DeltaConf} Conf
49
+ * @typedef {import('../observable.js').ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void, destroy: (rdt: RDT<Conf>) => void }> & {
50
+ * $delta: Schema<delta.Delta<Conf>>,
51
+ * delta: delta.Delta<Conf>,
52
+ * applyDelta: (delta: delta.Delta<Conf>, origin?: any) => delta.DeltaBuilder<Conf> | null,
25
53
  * destroy: () => void
26
54
  * }} RDT
27
55
  */
@@ -37,31 +65,39 @@ export const $rdt: Schema<RDT<any>>;
37
65
  /**
38
66
  * Connects two RDTs so that changes on either side are transformed and reflected on the other.
39
67
  *
40
- * @template {import('./delta.js').DeltaAny} DeltaA
68
+ * The {@link dt.Transformer transformer} it drives **manipulates deltas in place** (it splices op lists
69
+ * and rebases nodes directly, rather than only via `apply`/`rebase`) — a deliberate exception to the
70
+ * general rule that deltas are edited through `apply`/`rebase`. Consequently every change handed to it
71
+ * must be a privately-owned, fully-mutable delta, so the binding {@link delta.cloneDeep deep-clones}
72
+ * each incoming change (which is a shared read delta — an event payload or a snapshot) before the
73
+ * transformer touches it. See the initial-state sync below and {@link propagate}.
74
+ *
75
+ * @template {delta.DeltaConf} A
76
+ * @template {delta.DeltaConf} B
41
77
  */
42
- export class Binding<DeltaA extends import("./delta.js").DeltaAny> {
78
+ export class Binding<A extends delta.DeltaConf, B extends delta.DeltaConf> {
43
79
  /**
44
- * @param {RDT<DeltaA>} a
45
- * @param {RDT<import('./delta.js').DeltaAny>} b
46
- * @param {dt.TemplateFactory<any,any>} [template] a `$d => Template` factory; defaults to the
80
+ * @param {RDT<A>} a
81
+ * @param {RDT<B>} b
82
+ * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory; defaults to the
47
83
  * {@link dt.id identity} transformer. `bind` injects `a`'s schema, then materializes via `.init()`.
48
84
  */
49
- constructor(a: RDT<DeltaA>, b: RDT<import("./delta.js").DeltaAny>, template?: dt.TemplateFactory<any, any>);
85
+ constructor(a: RDT<A>, b: RDT<B>, template?: dt.TemplateFactory<A, B>);
50
86
  /**
51
- * @type {dt.Transformer<any,any>}
87
+ * @type {dt.Transformer<A,B>}
52
88
  */
53
- t: dt.Transformer<any, any>;
89
+ t: dt.Transformer<A, B>;
54
90
  /**
55
- * @type {RDT<any>}
91
+ * @type {RDT<A>}
56
92
  */
57
- a: RDT<any>;
93
+ a: RDT<A>;
58
94
  /**
59
- * @type {RDT<any>}
95
+ * @type {RDT<B>}
60
96
  */
61
- b: RDT<any>;
97
+ b: RDT<B>;
62
98
  _mux: mux.mutex;
63
- _achanged: (delta: any) => void;
64
- _bchanged: (delta: any) => void;
99
+ _achanged: (delta: delta.Delta<A>, origin: any) => void;
100
+ _bchanged: (delta: delta.Delta<B>, origin: any) => void;
65
101
  /**
66
102
  * Tear the binding down: unsubscribe both sides' `'delta'` and `'destroy'` listeners so changes are no
67
103
  * longer propagated. Call this when you no longer need the two RDTs kept in sync. It is also invoked
@@ -69,7 +105,7 @@ export class Binding<DeltaA extends import("./delta.js").DeltaAny> {
69
105
  */
70
106
  destroy: () => void;
71
107
  }
72
- export function bind<DeltaA extends import("./delta.js").DeltaAny>(a: RDT<DeltaA>, b: RDT<import("./delta.js").DeltaAny>, template?: dt.TemplateFactory<any, any>): Binding<DeltaA>;
108
+ export function bind<A extends delta.DeltaConf, B extends delta.DeltaConf>(a: RDT<A>, b: RDT<B>, template?: dt.TemplateFactory<A, B>): Binding<A, B>;
73
109
  export type Schema<T> = import("../schema.js").Schema<T>;
74
110
  /**
75
111
  * Abstract interface for a delta-based Replicated Data Type.
@@ -79,21 +115,50 @@ export type Schema<T> = import("../schema.js").Schema<T>;
79
115
  * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign
80
116
  * deltas via `applyDelta`.
81
117
  *
82
- * `applyDelta(d)` applies `d` and then, if the change would violate the RDT's own invariants, applies
83
- * a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns that fix
84
- * — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding} maps it
85
- * onto the other side just like any other change. `applyDelta` also emits the *effective* change
118
+ * `applyDelta(d, origin)` applies `d` and then, if the change would violate the RDT's own invariants,
119
+ * applies a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns
120
+ * that fix — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding}
121
+ * maps it onto the other side just like any other change. `applyDelta` also emits the *effective* change
86
122
  * (`d` together with the fix) on the `'delta'` channel.
123
+ *
124
+ * ## Origins
125
+ *
126
+ * Every `'delta'` event carries a second argument, `origin` — an opaque `any` value (modelled after
127
+ * {@link https://docs.yjs.dev/api/transactions Yjs transaction origins}) that identifies **where the
128
+ * change came from**. Whoever produces the change sets it, and it is usually the producing instance
129
+ * itself: a communication provider, an editor binding, or the {@link Binding} that mapped the change
130
+ * over from the other side. A change an RDT observes locally (e.g. a `MutationObserver` firing on a DOM
131
+ * edit) uses that RDT itself as the origin.
132
+ *
133
+ * Origins let a listener tell **its own** changes apart from foreign ones so it can skip the ones it
134
+ * already knows about — a change it produced is not looped back to where it came from. The canonical use
135
+ * is a network provider: it tags every remote update it applies with itself (`rdt.applyDelta(update,
136
+ * this)`) and then ignores `'delta'` events whose `origin === this`, so it never re-broadcasts an update
137
+ * it just received. The `origin` argument to `applyDelta` is optional and defaults to `null` (an
138
+ * anonymous/local change); the emitted event always carries whatever value was supplied.
139
+ *
140
+ * ## `Delta` vs `DeltaBuilder`
141
+ *
142
+ * The interface is parameterized by a {@link import ('./delta.js').DeltaConf DeltaConf} (like a
143
+ * {@link import ('./transformer.js').Transformer transformer}). Almost everything it exposes is the
144
+ * *read* form — {@link import ('./delta.js').Delta Delta} — because those values are **shared** and must
145
+ * not be mutated in place: `$delta` is the delta schema, the `delta` getter returns a state snapshot,
146
+ * `applyDelta` reads a change into the RDT, and the `'delta'` event payload is broadcast to every
147
+ * listener. The one *owned, mutable* form — {@link import ('./delta.js').DeltaBuilder DeltaBuilder} — is
148
+ * the fix `applyDelta` returns to the {@link Binding}. The binding never mutates a shared read delta: it
149
+ * {@link import ('./delta.js').cloneDeep deep-clones} every change into a private builder before the
150
+ * transformer rebases it (transformers manipulate deltas in place — see {@link Binding}).
87
151
  */
88
- export type RDT<D extends import("./delta.js").DeltaAny> = import("../observable.js").ObservableV2<{
89
- delta: (delta: D) => void;
90
- destroy: (rdt: RDT<D>) => void;
152
+ export type RDT<Conf extends import("./delta.js").DeltaConf> = import("../observable.js").ObservableV2<{
153
+ delta: (delta: delta.Delta<Conf>, origin: any) => void;
154
+ destroy: (rdt: RDT<Conf>) => void;
91
155
  }> & {
92
- $delta: Schema<D>;
93
- delta: D;
94
- applyDelta: (delta: D) => D | null;
156
+ $delta: Schema<delta.Delta<Conf>>;
157
+ delta: delta.Delta<Conf>;
158
+ applyDelta: (delta: delta.Delta<Conf>, origin?: any) => delta.DeltaBuilder<Conf> | null;
95
159
  destroy: () => void;
96
160
  };
161
+ import * as delta from './delta.js';
97
162
  import * as dt from './transformer.js';
98
163
  import * as mux from '../mutex.js';
99
164
  export { $domDelta, domRDT } from "./rdt/dom.js";
@@ -1,6 +1,21 @@
1
1
  export function $tresult<A extends import("../delta.js").DeltaConf, B extends import("../delta.js").DeltaConf>($a: s.Schema<import("../delta.js").Delta<A>>, $b: s.Schema<import("../delta.js").Delta<B>>): s.Schema<TransformResult<A, B>>;
2
2
  export function createTransformResult<DeltaA extends import("../delta.js").DeltaBuilderAny = delta.DeltaBuilderAny, DeltaB extends import("../delta.js").DeltaBuilderAny = delta.DeltaBuilderAny>(a: DeltaA | null, b: DeltaB | null): TransformResult<any, any>;
3
3
  /**
4
+ * A stateful, schema-bound mapper between changes of side A and side B, materialized from a
5
+ * {@link Template} via `init()`.
6
+ *
7
+ * ## Deltas are manipulated in place
8
+ *
9
+ * `apply`/`applyA`/`applyB` **mutate their input deltas directly** — they rebase nodes and splice op
10
+ * lists in place, and some transformers `apply(other, { move: true })` the input into an accumulator —
11
+ * rather than editing only through the copy-on-write `apply`/`rebase` surface. This is a deliberate
12
+ * exception to the general rule (a delta is normally edited only via `apply`/`rebase`, which re-clone
13
+ * frozen content on first touch); it keeps the transform allocation-free, but it means a transformer is
14
+ * an *owning* consumer of the delta it is given. **Callers must pass a privately-owned, fully-mutable
15
+ * delta** — never a shared or `done` (frozen) one. A caller holding a shared read delta must
16
+ * {@link import('../delta.js').cloneDeep deep-clone} it first (this is exactly what a
17
+ * {@link import('../rdt.js').Binding} does before routing a change through its transformer).
18
+ *
4
19
  * @template {import('../delta.js').DeltaConf} A
5
20
  * @template {import('../delta.js').DeltaConf} B
6
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lib0",
3
- "version": "1.0.0-rc.19",
3
+ "version": "1.0.0-rc.20",
4
4
  "description": "",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -331,7 +331,7 @@
331
331
  }
332
332
  },
333
333
  "devDependencies": {
334
- "@types/node": "^25.5.2",
334
+ "@types/node": "^26.0.0",
335
335
  "c8": "^11.0.0",
336
336
  "dpdm": "^4.0.1",
337
337
  "jsdom": "^29.1.1",
@@ -1596,6 +1596,101 @@ export const slice = (d, start = 0, end = d.childCnt, currNode = d.children.star
1596
1596
  */
1597
1597
  export const clone = d => /** @type {any} */ (slice(d, 0, d.childCnt))
1598
1598
 
1599
+ /**
1600
+ * Deep-clone a value that may be a nested delta: a fresh {@link cloneDeep} of it when it is a delta,
1601
+ * otherwise the value unchanged (plain JSON content is treated as immutable and shared).
1602
+ *
1603
+ * @param {any} v
1604
+ * @return {any}
1605
+ */
1606
+ const _cloneMaybeDeltaDeep = v => $deltaAny.check(v) ? cloneDeep(v) : v
1607
+
1608
+ /**
1609
+ * Deep-clone one content (child) op for {@link cloneDeep}: a fresh op whose nested deltas (an insert's
1610
+ * delta content, a modify's value) are themselves deep-cloned. `format`/`attribution` objects are
1611
+ * retained (shared) — they are always copied before being mutated (see {@link cloneDeep}). Ops without a
1612
+ * nested delta a clone keeps (`text`/`retain`/`delete` — `delete` drops its prevValue) use `op.clone()`.
1613
+ *
1614
+ * @param {ChildrenOpAny} op
1615
+ * @return {ChildrenOpAny}
1616
+ */
1617
+ const _cloneChildOpDeep = op =>
1618
+ $insertOp.check(op)
1619
+ ? new InsertOp(op.insert.map(_cloneMaybeDeltaDeep), op.format, op.attribution)
1620
+ : ($modifyOp.check(op)
1621
+ ? new ModifyOp(cloneDeep(op.value), op.format, op.attribution)
1622
+ : op.clone())
1623
+
1624
+ /**
1625
+ * Deep-clone one attribute op for {@link cloneDeep} — the attribute-dimension mirror of
1626
+ * {@link _cloneChildOpDeep}: a `setAttr`'s value/prevValue and a `modifyAttr`'s value may be nested
1627
+ * deltas and are deep-cloned.
1628
+ *
1629
+ * @param {AttrOpAny} op
1630
+ * @return {AttrOpAny}
1631
+ */
1632
+ const _cloneAttrOpDeep = op =>
1633
+ $setAttrOp.check(op)
1634
+ ? new SetAttrOp(op.key, _cloneMaybeDeltaDeep(op.value), _cloneMaybeDeltaDeep(op.prevValue), op.attribution)
1635
+ : ($modifyAttrOp.check(op)
1636
+ ? new ModifyAttrOp(op.key, cloneDeep(op.value), op.attribution)
1637
+ // the only remaining attr op is a deleteAttr (its prevValue may be a nested delta)
1638
+ : new DeleteAttrOp(op.key, _cloneMaybeDeltaDeep(op.prevValue), op.attribution))
1639
+
1640
+ /**
1641
+ * A **deep** clone of `d`: like {@link clone}, but every nested delta — an insert's delta content, a
1642
+ * `modify`/`modifyAttr` value, a delta-valued attribute — is itself recursively cloned into a fresh,
1643
+ * **mutable** node, instead of being frozen (`done`) and shared as {@link clone} does. The result and
1644
+ * its whole subtree are therefore independently editable.
1645
+ *
1646
+ * ## What is cloned, and when to reach for this
1647
+ *
1648
+ * A delta owns two kinds of nested state, treated differently on purpose:
1649
+ * - **Ops and (nested) deltas are cloned.** They carry structure edited *in place* — `apply`/`rebase`
1650
+ * splice op lists and mutate op fields — so a shared copy would be corrupted by a later edit of either
1651
+ * side.
1652
+ * - **`format`/`attribution` objects and plain JSON insert values are retained (shared).** They are
1653
+ * always *copied before being changed* (a format/attribution update allocates a new object — see
1654
+ * {@link mergeAttr}), so sharing them is safe and avoids needless allocation.
1655
+ *
1656
+ * {@link clone} suffices for the common case because a delta is only ever edited through `apply`/`rebase`,
1657
+ * which re-clone a `done` nested delta the first time they touch it (see {@link InsertOp#_modValue}) — the
1658
+ * shared, frozen children are never mutated in place. Reach for `cloneDeep` when a nested delta will be
1659
+ * **manipulated directly** instead of via `apply`/`rebase` — most notably by a
1660
+ * {@link import('./transformer/core.js').Transformer transformer}, which walks and mutates a delta's
1661
+ * ops/children in place. A `done` (frozen) delta must always be cloned before such direct manipulation
1662
+ * (a plain `apply`/`rebase` handles the freeze itself, so it needs no pre-clone).
1663
+ *
1664
+ * @template {DeltaConf} Conf
1665
+ * @param {Delta<Conf>} d
1666
+ * @return {DeltaBuilder<Conf>}
1667
+ */
1668
+ export const cloneDeep = d => {
1669
+ const cpy = /** @type {DeltaAny} */ (new DeltaBuilder(d.name, d.$schema))
1670
+ cpy.origin = d.origin
1671
+ cpy.isFinal = d.isFinal
1672
+ for (const op of d.attrs) {
1673
+ // @ts-ignore dynamic attr key (the same limitation `slice` suppresses when copying attrs)
1674
+ cpy.attrs[op.key] = _cloneAttrOpDeep(op)
1675
+ }
1676
+ for (const op of d.children) {
1677
+ list.pushEnd(cpy.children, _cloneChildOpDeep(op))
1678
+ }
1679
+ cpy.childCnt = d.childCnt
1680
+ // marks ride along verbatim — a full clone never clamps number-keyed root marks, and Mark objects are
1681
+ // immutable so they are shared (mirrors slice's full-copy path); a change delta's deleteMarks is copied.
1682
+ if (d.maybeHasMarks) {
1683
+ if (d.marks !== null) {
1684
+ const cpyMarks = new Marks()
1685
+ for (const m of d.marks) cpyMarks.add(m)
1686
+ if (cpyMarks.size > 0) cpy.marks = cpyMarks
1687
+ }
1688
+ cpy.maybeHasMarks = true
1689
+ }
1690
+ if (d.deleteMarks !== null) cpy.deleteMarks = new Set(d.deleteMarks)
1691
+ return /** @type {any} */ (cpy)
1692
+ }
1693
+
1599
1694
  /**
1600
1695
  * A *shallow* clone of `d`: a fresh {@link DeltaBuilder} carrying `d`'s name, attribute ops, and own
1601
1696
  * (root) marks — but **no children**. Content-transforming
@@ -3695,6 +3790,12 @@ class _DiffStringWrapper {
3695
3790
  * @property {(d1: DeltaAny, d2: DeltaAny) => boolean} [compare] Predicate deciding when two nodes
3696
3791
  * are paired into a `modify` (vs. replaced wholesale). Defaults to comparing names
3697
3792
  * (`(d1, d2) => d1.name === d2.name`). Called as `compare(fromNode, toNode)`.
3793
+ * @property {boolean} [clone=false] By default `diff` does **not** produce a fully fresh delta: wherever
3794
+ * it emits content from `d2` — a wholesale-inserted child node, or a delta-valued attribute — it *shares*
3795
+ * that nested delta by reference, so the result aliases `d2`'s subtrees. Set `true` to
3796
+ * {@link cloneDeep deep-clone} every such delta (children **and** attributes) as it is included in the
3797
+ * output, yielding a result that shares no structure with `d2` (and is safe to hand to something that
3798
+ * manipulates deltas in place, e.g. a transformer).
3698
3799
  */
3699
3800
 
3700
3801
  /**
@@ -3880,8 +3981,9 @@ export const diff = (d1, d2, options = {}) => {
3880
3981
  // while modifyAttr's param is typed Attribution for API ergonomics
3881
3982
  d.modifyAttr(key, diff(prevVal, nextVal, options), /** @type {Attribution|null|undefined} */ (diffDim(attr1?.attribution, attr2.attribution, true)))
3882
3983
  } else {
3883
- // setAttr replaces the whole attr, so it carries the new attribution as data (object-or-none)
3884
- d.setAttr(key, nextVal, attr2.attribution)
3984
+ // setAttr replaces the whole attr, so it carries the new attribution as data (object-or-none).
3985
+ // `nextVal` is shared from `d2`; deep-clone it when the caller wants an independent result.
3986
+ d.setAttr(key, options.clone ? _cloneMaybeDeltaDeep(nextVal) : nextVal, attr2.attribution)
3885
3987
  }
3886
3988
  /* c8 ignore start */
3887
3989
  } else {
@@ -3923,13 +4025,16 @@ const contentLen = c => typeof c === 'string' ? c.length : 1
3923
4025
  */
3924
4026
  const applyRemoves = (d, crem, len) => { len > 0 && d.delete(crem.splice(0, len).map(contentLen).reduce(math.add, 0)) }
3925
4027
  /**
3926
- * Apply inserts from c.insert to d, mutating c.insert to exclude the applied items
4028
+ * Apply inserts from c.insert to d, mutating c.insert to exclude the applied items. A wholesale-inserted
4029
+ * child is a nested delta shared from `d2`; deep-clone it when `cloneChildren` (the diff's `clone` option)
4030
+ * so the result aliases nothing in `d2` (see {@link DiffOptions}).
3927
4031
  *
3928
4032
  * @param {DeltaBuilderAny} d
3929
4033
  * @param {Array<any>} cins
3930
4034
  * @param {number} len
4035
+ * @param {boolean} [cloneChildren]
3931
4036
  */
3932
- const applyInserts = (d, cins, len) => { len > 0 && cins.splice(0, len).forEach(ins => d.insert(typeof ins === 'string' ? ins : [ins instanceof _DiffStringWrapper ? ins.str : ins])) }
4037
+ const applyInserts = (d, cins, len, cloneChildren) => { len > 0 && cins.splice(0, len).forEach(ins => d.insert(typeof ins === 'string' ? ins : [ins instanceof _DiffStringWrapper ? ins.str : (cloneChildren ? _cloneMaybeDeltaDeep(ins) : ins)])) }
3933
4038
 
3934
4039
  /**
3935
4040
  * @param {DeltaBuilderAny} d
@@ -3954,13 +4059,13 @@ const applyChangesetToDelta = (d, changeset, compare, options) => {
3954
4059
  continue
3955
4060
  }
3956
4061
  applyRemoves(d, c.remove, cremoveDeltaIndex)
3957
- applyInserts(d, c.insert, cinsertDeltaIndex)
4062
+ applyInserts(d, c.insert, cinsertDeltaIndex, options.clone)
3958
4063
  d.modify(diff(c.remove[0], c.insert[0], options))
3959
4064
  c.remove.splice(0, 1)
3960
4065
  c.insert.splice(0, 1)
3961
4066
  }
3962
4067
  applyRemoves(d, c.remove, c.remove.length)
3963
- applyInserts(d, c.insert, c.insert.length)
4068
+ applyInserts(d, c.insert, c.insert.length, options.clone)
3964
4069
  }
3965
4070
  return d
3966
4071
  }
@@ -2,8 +2,9 @@
2
2
  * # In-memory delta RDT
3
3
  *
4
4
  * {@link deltaRDT} creates an RDT (see `../rdt.js`) whose state is the accumulated delta. Calling
5
- * `applyDelta` merges the incoming delta into `state` and re-emits it as a `'delta'` event, so it can
6
- * be bound to any other RDT. It accepts every valid delta as-is, so it never returns a fix.
5
+ * `applyDelta(delta, origin)` merges the incoming delta into `state` and re-emits it (with the given
6
+ * {@link import('../rdt.js').RDT origin}) as a `'delta'` event, so it can be bound to any other RDT. It
7
+ * accepts every valid delta as-is, so it never returns a fix.
7
8
  *
8
9
  * `state` is kept as a **final** delta (`isFinal`): it represents the current document, so a `delete`
9
10
  * or `deleteAttr` removes the content/attribute outright instead of accumulating a delete-op marker.
@@ -22,21 +23,21 @@ import * as mux from '../../mutex.js'
22
23
  */
23
24
 
24
25
  /**
25
- * @template {delta.DeltaAny} D
26
- * @typedef {import('../rdt.js').RDT<D>} RDT
26
+ * @template {delta.DeltaConf} Conf
27
+ * @typedef {import('../rdt.js').RDT<Conf>} RDT
27
28
  */
28
29
 
29
30
  /**
30
31
  * An in-memory RDT whose state is the accumulated delta. Calling `applyDelta` merges the incoming delta
31
32
  * into `state` (via {@link delta.DeltaBuilder#apply}) and re-emits it as a `'delta'`.
32
33
  *
33
- * @template {delta.DeltaAny} D
34
- * @implements {RDT<D>}
35
- * @extends {ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: DeltaRDT<D>) => void }>}
34
+ * @template {delta.DeltaConf} Conf
35
+ * @implements {RDT<Conf>}
36
+ * @extends {ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void, destroy: (rdt: DeltaRDT<Conf>) => void }>}
36
37
  */
37
38
  class DeltaRDT extends ObservableV2 {
38
39
  /**
39
- * @param {Schema<D>} $delta schema of the deltas this RDT produces
40
+ * @param {Schema<delta.Delta<Conf>>} $delta schema of the deltas this RDT produces
40
41
  */
41
42
  constructor ($delta) {
42
43
  super()
@@ -52,10 +53,13 @@ class DeltaRDT extends ObservableV2 {
52
53
  * Merge an incoming delta into the current state and notify observers. Always returns `null`: this
53
54
  * RDT accepts every valid delta unchanged, so it never produces a fix.
54
55
  *
55
- * @param {D} d
56
+ * @param {delta.Delta<Conf>} d
57
+ * @param {any} [origin] who produced the change; forwarded verbatim on the emitted `'delta'` event so
58
+ * listeners can recognise (and skip) changes they made themselves — see {@link RDT} “Origins”. Defaults
59
+ * to `null` (an anonymous/local change).
56
60
  * @return {null}
57
61
  */
58
- applyDelta (d) {
62
+ applyDelta (d, origin = null) {
59
63
  if (d.isEmpty()) return null
60
64
  this._mux(() => {
61
65
  if (this.state == null) {
@@ -69,8 +73,9 @@ class DeltaRDT extends ObservableV2 {
69
73
  // emit AFTER releasing the mutex. When bound to a fix-producing RDT, that side's fix is mapped
70
74
  // back here and applied via a synchronous re-entrant `applyDelta` while this call is still on the
71
75
  // stack; with the emit inside the mutex that re-entrant mutation would be dropped. The binding's
72
- // own mutex still breaks the echo loop.
73
- this.emit('delta', [d])
76
+ // own mutex still breaks the echo loop. `d` is emitted as a shared read delta (the RDT only read it
77
+ // into `state`); a consumer that needs to mutate it deep-clones it first (see the `RDT` typedef).
78
+ this.emit('delta', [d, origin])
74
79
  return null
75
80
  }
76
81
 
@@ -78,10 +83,11 @@ class DeltaRDT extends ObservableV2 {
78
83
  * The current state as a delta: the accumulated `state`, or an empty delta when nothing has been
79
84
  * applied yet.
80
85
  *
81
- * @return {D}
86
+ * @return {delta.Delta<Conf>}
82
87
  */
83
88
  get delta () {
84
- return /** @type {any} */ (this.state ?? delta.create(this.$delta))
89
+ // `state` is a loosely-typed `DeltaBuilderAny`; narrow it to this RDT's read `Delta<Conf>` view.
90
+ return /** @type {delta.Delta<Conf>} */ (/** @type {unknown} */ (this.state ?? delta.create(this.$delta)))
85
91
  }
86
92
 
87
93
  destroy () {
@@ -93,7 +99,7 @@ class DeltaRDT extends ObservableV2 {
93
99
  /**
94
100
  * Create a {@link DeltaRDT} for deltas described by `$delta`.
95
101
  *
96
- * @template {delta.DeltaAny} D
97
- * @param {Schema<D>} $delta
102
+ * @template {delta.DeltaConf} Conf
103
+ * @param {Schema<delta.Delta<Conf>>} $delta
98
104
  */
99
105
  export const deltaRDT = $delta => new DeltaRDT($delta)
@@ -5,8 +5,9 @@
5
5
  *
6
6
  * {@link domRDT} creates an RDT (see `../rdt.js`) backed by a live DOM subtree. DOM mutations are
7
7
  * observed with a `MutationObserver`, turned into deltas (by diffing the new DOM state against the
8
- * last-known one) and emitted as `'delta'` events; incoming deltas are applied back onto the DOM.
9
- * This lets a DOM subtree be bound to any other RDT.
8
+ * last-known one) and emitted as `'delta'` events (with the RDT itself as their
9
+ * {@link import('../rdt.js').RDT origin}); incoming deltas are applied back onto the DOM. This lets a
10
+ * DOM subtree be bound to any other RDT.
10
11
  *
11
12
  * @module delta/rdt/dom
12
13
  */
@@ -24,8 +25,8 @@ import * as s from '../../schema.js'
24
25
  */
25
26
 
26
27
  /**
27
- * @template {delta.DeltaAny} D
28
- * @typedef {import('../rdt.js').RDT<D>} RDT
28
+ * @template {delta.DeltaConf} Conf
29
+ * @typedef {import('../rdt.js').RDT<Conf>} RDT
29
30
  */
30
31
 
31
32
  /**
@@ -177,16 +178,21 @@ const applyDeltaToDom = (el, d) => {
177
178
  export const $domDelta = /* @__PURE__ */ delta.$delta({ name: s.$string, attrs: s.$record(s.$string, s.$string), children: s.$never, text: true, recursiveChildren: true })
178
179
 
179
180
  /**
180
- * @typedef {delta.Delta<{ name: string, attrs: { [key:string]: string }, text: true, recursiveChildren: true }>} DomDelta
181
+ * The {@link delta.DeltaConf} of the deltas a {@link DomRDT} produces/consumes (see {@link $domDelta}).
182
+ *
183
+ * @typedef {{ name: string, attrs: { [key:string]: string }, text: true, recursiveChildren: true }} DomConf
184
+ */
185
+
186
+ /**
187
+ * @typedef {delta.Delta<DomConf>} DomDelta
181
188
  */
182
189
 
183
190
  /**
184
191
  * An RDT backed by a live DOM subtree. DOM mutations observed via `MutationObserver` are diffed
185
192
  * against the last-known state and emitted as deltas; incoming deltas are applied back onto the DOM.
186
193
  *
187
- * @template {DomDelta} [D=DomDelta]
188
- * @implements {RDT<D>}
189
- * @extends {ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: DomRDT<D>) => void }>}
194
+ * @implements {RDT<DomConf>}
195
+ * @extends {ObservableV2<{ delta: (delta: delta.Delta<DomConf>, origin: any) => void, destroy: (rdt: DomRDT) => void }>}
190
196
  */
191
197
  class DomRDT extends ObservableV2 {
192
198
  /**
@@ -195,7 +201,7 @@ class DomRDT extends ObservableV2 {
195
201
  constructor (observedNode) {
196
202
  super()
197
203
  /**
198
- * @type {Schema<D>}
204
+ * @type {Schema<DomDelta>}
199
205
  */
200
206
  this.$delta = /** @type {any} */ ($domDelta)
201
207
  this.observedNode = observedNode
@@ -233,8 +239,9 @@ class DomRDT extends ObservableV2 {
233
239
  */
234
240
  _mutationHandler = mutations => {
235
241
  if (mutations.length === 0) return
236
- const change = /** @type {D} */ (this._pull())
237
- if (!change.isEmpty()) this.emit('delta', [change])
242
+ const change = this._pull()
243
+ // a locally-observed DOM edit: this RDT is the producer, so it is the origin (see {@link RDT})
244
+ if (!change.isEmpty()) this.emit('delta', [change, this])
238
245
  }
239
246
 
240
247
  /**
@@ -245,10 +252,14 @@ class DomRDT extends ObservableV2 {
245
252
  * `d` rebased onto `b` is applied to the DOM, and `b` rebased onto `d` is returned as the fix so the
246
253
  * binding pipes it back through the transformer to the other side.
247
254
  *
248
- * @param {D} d
249
- * @return {D | null} the rebased local change (`b`), or `null` when there were no concurrent edits
255
+ * @param {delta.Delta<DomConf>} d
256
+ * @param {any} [origin] who produced `d`; forwarded verbatim on the emitted `'delta'` event so
257
+ * listeners can recognise (and skip) their own changes — see {@link RDT} “Origins”. Defaults to `null`
258
+ * (an anonymous/local change).
259
+ * @return {delta.DeltaBuilder<DomConf> | null} the rebased local change (`b`), or `null` when there
260
+ * were no concurrent edits
250
261
  */
251
- applyDelta (d) {
262
+ applyDelta (d, origin = null) {
252
263
  const b = this._pull()
253
264
  /** @type {DomDelta} */
254
265
  let toApply = d
@@ -266,19 +277,19 @@ class DomRDT extends ObservableV2 {
266
277
  // echo of our own write — draining the self-caused records here is what prevents it.
267
278
  this.observer.takeRecords()
268
279
  // Forward the effective change so a chained binding on the other side picks it up; the binding
269
- // that fed us `d` swallows this re-emit via its own mutex (see ../rdt.js).
270
- const effective = /** @type {D} */ (toApply)
271
- this.emit('delta', [effective])
272
- return /** @type {D?} */ (fix)
280
+ // that fed us `d` swallows this re-emit via its own mutex (see ../rdt.js). `fix` is a freshly
281
+ // rebased builder at runtime, returned as the owned change the binding maps on.
282
+ this.emit('delta', [toApply, origin])
283
+ return /** @type {delta.DeltaBuilder<DomConf>?} */ (fix)
273
284
  }
274
285
 
275
286
  /**
276
287
  * The current state as a delta: an "insert everything" delta describing the observed subtree.
277
288
  *
278
- * @return {D}
289
+ * @return {delta.Delta<DomConf>}
279
290
  */
280
291
  get delta () {
281
- return /** @type {D} */ (domToDelta(this.observedNode))
292
+ return domToDelta(this.observedNode)
282
293
  }
283
294
 
284
295
  destroy () {
package/src/delta/rdt.js CHANGED
@@ -4,8 +4,9 @@
4
4
  * An **RDT** ("replicated data type") is any object that represents some state and communicates changes
5
5
  * to that state as {@link import('./delta.js').Delta deltas}. Every RDT is an
6
6
  * {@link import('../observable.js').ObservableV2 observable}:
7
- * - it **emits** a `'delta'` event carrying a delta whenever its own state changes, and
8
- * - it **accepts** foreign changes through `applyDelta(delta)`, which mutates its state to match.
7
+ * - it **emits** a `'delta'` event carrying a delta and its {@link RDT origin} whenever its own state
8
+ * changes, and
9
+ * - it **accepts** foreign changes through `applyDelta(delta, origin)`, which mutates its state to match.
9
10
  *
10
11
  * Because the changes are deltas, two different representations of "the same" data can be kept in sync
11
12
  * even when their shapes differ. That is the job of a {@link Binding}: it connects two RDTs `a` and `b`
@@ -64,17 +65,45 @@ export { $domDelta, domRDT } from './rdt/dom.js'
64
65
  * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign
65
66
  * deltas via `applyDelta`.
66
67
  *
67
- * `applyDelta(d)` applies `d` and then, if the change would violate the RDT's own invariants, applies
68
- * a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns that fix
69
- * — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding} maps it
70
- * onto the other side just like any other change. `applyDelta` also emits the *effective* change
68
+ * `applyDelta(d, origin)` applies `d` and then, if the change would violate the RDT's own invariants,
69
+ * applies a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns
70
+ * that fix — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding}
71
+ * maps it onto the other side just like any other change. `applyDelta` also emits the *effective* change
71
72
  * (`d` together with the fix) on the `'delta'` channel.
72
73
  *
73
- * @template {import('./delta.js').DeltaAny} D
74
- * @typedef {import('../observable.js').ObservableV2<{ delta: (delta: D) => void, destroy: (rdt: RDT<D>) => void }> & {
75
- * $delta: Schema<D>,
76
- * delta: D,
77
- * applyDelta: (delta: D) => D | null,
74
+ * ## Origins
75
+ *
76
+ * Every `'delta'` event carries a second argument, `origin` — an opaque `any` value (modelled after
77
+ * {@link https://docs.yjs.dev/api/transactions Yjs transaction origins}) that identifies **where the
78
+ * change came from**. Whoever produces the change sets it, and it is usually the producing instance
79
+ * itself: a communication provider, an editor binding, or the {@link Binding} that mapped the change
80
+ * over from the other side. A change an RDT observes locally (e.g. a `MutationObserver` firing on a DOM
81
+ * edit) uses that RDT itself as the origin.
82
+ *
83
+ * Origins let a listener tell **its own** changes apart from foreign ones so it can skip the ones it
84
+ * already knows about — a change it produced is not looped back to where it came from. The canonical use
85
+ * is a network provider: it tags every remote update it applies with itself (`rdt.applyDelta(update,
86
+ * this)`) and then ignores `'delta'` events whose `origin === this`, so it never re-broadcasts an update
87
+ * it just received. The `origin` argument to `applyDelta` is optional and defaults to `null` (an
88
+ * anonymous/local change); the emitted event always carries whatever value was supplied.
89
+ *
90
+ * ## `Delta` vs `DeltaBuilder`
91
+ *
92
+ * The interface is parameterized by a {@link import('./delta.js').DeltaConf DeltaConf} (like a
93
+ * {@link import('./transformer.js').Transformer transformer}). Almost everything it exposes is the
94
+ * *read* form — {@link import('./delta.js').Delta Delta} — because those values are **shared** and must
95
+ * not be mutated in place: `$delta` is the delta schema, the `delta` getter returns a state snapshot,
96
+ * `applyDelta` reads a change into the RDT, and the `'delta'` event payload is broadcast to every
97
+ * listener. The one *owned, mutable* form — {@link import('./delta.js').DeltaBuilder DeltaBuilder} — is
98
+ * the fix `applyDelta` returns to the {@link Binding}. The binding never mutates a shared read delta: it
99
+ * {@link import('./delta.js').cloneDeep deep-clones} every change into a private builder before the
100
+ * transformer rebases it (transformers manipulate deltas in place — see {@link Binding}).
101
+ *
102
+ * @template {import('./delta.js').DeltaConf} Conf
103
+ * @typedef {import('../observable.js').ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void, destroy: (rdt: RDT<Conf>) => void }> & {
104
+ * $delta: Schema<delta.Delta<Conf>>,
105
+ * delta: delta.Delta<Conf>,
106
+ * applyDelta: (delta: delta.Delta<Conf>, origin?: any) => delta.DeltaBuilder<Conf> | null,
78
107
  * destroy: () => void
79
108
  * }} RDT
80
109
  */
@@ -101,47 +130,62 @@ export const $rdt = /** @type {Schema<RDT<any>>} */ (/* @__PURE__ */ s.$custom(o
101
130
  * `ta` / `tb` are changes that have ALREADY been applied to `a` / `b` (an incoming `'delta'`, or a fix
102
131
  * returned by a previous `applyDelta`). Each is mapped onto the other side via the transformer —
103
132
  * `apply` rebases the two against each other — and the mapped results are applied, which may yield
104
- * further RDT fixes. We repeat until neither side reports a fix.
105
- *
106
- * @param {Binding<any>} binding
107
- * @param {any} ta change already applied on `a` (or `null`)
108
- * @param {any} tb change already applied on `b` (or `null`)
133
+ * further RDT fixes. We repeat until neither side reports a fix. Every change it applies to either side
134
+ * uses the `binding` itself as its {@link RDT origin} — from each side's perspective the binding is what
135
+ * produced the mapped change.
136
+ *
137
+ * `ta`/`tb` are shared read deltas (an event payload, or a fix that was also written into the producing
138
+ * RDT's state), so they are {@link delta.cloneDeep deep-cloned} into private builders before the
139
+ * transformer — which rebases its inputs in place — touches them (see {@link Binding}).
140
+ *
141
+ * @template {delta.DeltaConf} A
142
+ * @template {delta.DeltaConf} B
143
+ * @param {Binding<A,B>} binding
144
+ * @param {any} ta change already applied on `a` — a shared read delta (event payload or a fix), or `null`
145
+ * @param {any} tb change already applied on `b` — a shared read delta (event payload or a fix), or `null`
109
146
  */
110
147
  const propagate = (binding, ta, tb) => {
111
148
  while ((ta != null && !ta.isEmpty()) || (tb != null && !tb.isEmpty())) {
112
- const tres = binding.t.apply(dt.createTransformResult(ta, tb))
113
- ta = tres.a != null ? binding.a.applyDelta(tres.a) : null
114
- tb = tres.b != null ? binding.b.applyDelta(tres.b) : null
149
+ const tres = binding.t.apply(dt.createTransformResult(
150
+ ta != null ? delta.cloneDeep(ta) : null,
151
+ tb != null ? delta.cloneDeep(tb) : null
152
+ ))
153
+ ta = tres.a != null ? binding.a.applyDelta(tres.a, binding) : null
154
+ tb = tres.b != null ? binding.b.applyDelta(tres.b, binding) : null
115
155
  }
116
156
  }
117
157
 
118
158
  /**
119
159
  * Connects two RDTs so that changes on either side are transformed and reflected on the other.
120
160
  *
121
- * @template {import('./delta.js').DeltaAny} DeltaA
161
+ * The {@link dt.Transformer transformer} it drives **manipulates deltas in place** (it splices op lists
162
+ * and rebases nodes directly, rather than only via `apply`/`rebase`) — a deliberate exception to the
163
+ * general rule that deltas are edited through `apply`/`rebase`. Consequently every change handed to it
164
+ * must be a privately-owned, fully-mutable delta, so the binding {@link delta.cloneDeep deep-clones}
165
+ * each incoming change (which is a shared read delta — an event payload or a snapshot) before the
166
+ * transformer touches it. See the initial-state sync below and {@link propagate}.
167
+ *
168
+ * @template {delta.DeltaConf} A
169
+ * @template {delta.DeltaConf} B
122
170
  */
123
171
  export class Binding {
124
172
  /**
125
- * @param {RDT<DeltaA>} a
126
- * @param {RDT<import('./delta.js').DeltaAny>} b
127
- * @param {dt.TemplateFactory<any,any>} [template] a `$d => Template` factory; defaults to the
173
+ * @param {RDT<A>} a
174
+ * @param {RDT<B>} b
175
+ * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory; defaults to the
128
176
  * {@link dt.id identity} transformer. `bind` injects `a`'s schema, then materializes via `.init()`.
129
177
  */
130
- constructor (a, b, template = dt.id) {
178
+ constructor (a, b, template = /** @type {dt.TemplateFactory<A,B>} */ (dt.id)) {
131
179
  /**
132
- * @type {dt.Transformer<any,any>}
180
+ * @type {dt.Transformer<A,B>}
133
181
  */
134
182
  this.t = template(a.$delta).init()
135
- // reason: the `'delta'` channel carries `Delta` values while the transformer is typed for the
136
- // mutable `DeltaBuilder` it consumes/produces, and the binding is generic over both sides; typing
137
- // the internal handles as `RDT<any>` lets changes pass through the transformer without unsound
138
- // cross-casts, while `bind`/the constructor keep precise public signatures.
139
183
  /**
140
- * @type {RDT<any>}
184
+ * @type {RDT<A>}
141
185
  */
142
186
  this.a = a
143
187
  /**
144
- * @type {RDT<any>}
188
+ * @type {RDT<B>}
145
189
  */
146
190
  this.b = b
147
191
  this._mux = mux.createMutex()
@@ -157,10 +201,17 @@ export class Binding {
157
201
  // time are NOT transferred to `b` here; marks ride only on subsequent live `applyA`/`applyB`.
158
202
  // Wrapped in the mutex so these `applyDelta` calls don't echo back through the listeners above.
159
203
  this._mux(() => {
160
- const tres = this.t.applyA(this.a.delta)
161
- const fa = tres.a ? this.a.applyDelta(tres.a) : null
162
- const diffB = tres.b ? delta.diff(/** @type {delta.DeltaAny} */ (this.b.delta), tres.b) : null
163
- const fb = diffB ? this.b.applyDelta(diffB) : null
204
+ // `a.delta` is a shared read snapshot (for an in-memory RDT it IS the live state, whose nested
205
+ // children are frozen `done`), so DEEP-clone it into a private, fully-mutable builder before the
206
+ // transformer which rebases its input in place projects it. A shallow `clone` would still share
207
+ // those frozen children and corrupt / fail on the live state (see `delta.cloneDeep`).
208
+ const tres = this.t.applyA(delta.cloneDeep(this.a.delta))
209
+ const fa = tres.a ? this.a.applyDelta(tres.a, this) : null
210
+ // `diff` shares nested children with `tres.b` by default, and `tres.b` is a (possibly stateful)
211
+ // transformer's output that may still alias its internal state — so `clone` keeps the diff
212
+ // independent, since it is then applied into `b` (which freezes it) and propagated onward.
213
+ const diffB = tres.b ? delta.diff(this.b.delta, tres.b, { clone: true }) : null
214
+ const fb = diffB ? this.b.applyDelta(diffB, this) : null
164
215
  propagate(this, fa, fb)
165
216
  })
166
217
  }
@@ -184,11 +235,12 @@ export class Binding {
184
235
  * {@link Binding#destroy} on the returned binding to stop syncing (it also self-destroys when either RDT
185
236
  * emits `'destroy'`).
186
237
  *
187
- * @template {import('./delta.js').DeltaAny} DeltaA
188
- * @param {RDT<DeltaA>} a
189
- * @param {RDT<import('./delta.js').DeltaAny>} b
190
- * @param {dt.TemplateFactory<any,any>} [template] a `$d => Template` factory (e.g. `dt.id` or
238
+ * @template {delta.DeltaConf} A
239
+ * @template {delta.DeltaConf} B
240
+ * @param {RDT<A>} a
241
+ * @param {RDT<B>} b
242
+ * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory (e.g. `dt.id` or
191
243
  * `$d => dt.renameAttrs($d, {a:'b'})`)
192
- * @return {Binding<DeltaA>}
244
+ * @return {Binding<A,B>}
193
245
  */
194
246
  export const bind = (a, b, template) => new Binding(a, b, template)
@@ -89,6 +89,21 @@ export const $tresult = ($a, $b) => /** @type {any} */ (s.$instanceOf(TransformR
89
89
  export const createTransformResult = (a, b) => new TransformResult(a, b)
90
90
 
91
91
  /**
92
+ * A stateful, schema-bound mapper between changes of side A and side B, materialized from a
93
+ * {@link Template} via `init()`.
94
+ *
95
+ * ## Deltas are manipulated in place
96
+ *
97
+ * `apply`/`applyA`/`applyB` **mutate their input deltas directly** — they rebase nodes and splice op
98
+ * lists in place, and some transformers `apply(other, { move: true })` the input into an accumulator —
99
+ * rather than editing only through the copy-on-write `apply`/`rebase` surface. This is a deliberate
100
+ * exception to the general rule (a delta is normally edited only via `apply`/`rebase`, which re-clone
101
+ * frozen content on first touch); it keeps the transform allocation-free, but it means a transformer is
102
+ * an *owning* consumer of the delta it is given. **Callers must pass a privately-owned, fully-mutable
103
+ * delta** — never a shared or `done` (frozen) one. A caller holding a shared read delta must
104
+ * {@link import('../delta.js').cloneDeep deep-clone} it first (this is exactly what a
105
+ * {@link import('../rdt.js').Binding} does before routing a change through its transformer).
106
+ *
92
107
  * @template {import('../delta.js').DeltaConf} A
93
108
  * @template {import('../delta.js').DeltaConf} B
94
109
  */