lib0 1.0.0-rc.2 → 1.0.0-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +0 -1315
  2. package/{types → dist}/buffer.d.ts +2 -10
  3. package/{types → dist}/delta/delta.d.ts +582 -144
  4. package/dist/delta/position.d.ts +65 -0
  5. package/dist/delta/rdt/delta.d.ts +57 -0
  6. package/dist/delta/rdt/dom.d.ts +102 -0
  7. package/dist/delta/rdt.d.ts +180 -0
  8. package/dist/delta/transformer/attr.d.ts +64 -0
  9. package/dist/delta/transformer/attribution-to-format.d.ts +52 -0
  10. package/dist/delta/transformer/children.d.ts +69 -0
  11. package/dist/delta/transformer/conform.d.ts +83 -0
  12. package/dist/delta/transformer/core.d.ts +189 -0
  13. package/dist/delta/transformer/full-attributions.d.ts +37 -0
  14. package/dist/delta/transformer/id.d.ts +2 -0
  15. package/dist/delta/transformer/inline.d.ts +89 -0
  16. package/dist/delta/transformer/pipe.d.ts +182 -0
  17. package/dist/delta/transformer/project.d.ts +223 -0
  18. package/dist/delta/transformer/rename-attrs.d.ts +75 -0
  19. package/dist/delta/transformer/rename.d.ts +41 -0
  20. package/dist/delta/transformer/value.d.ts +92 -0
  21. package/dist/delta/transformer.d.ts +14 -0
  22. package/dist/diff/patience.d.ts +25 -0
  23. package/{types → dist}/environment.d.ts +10 -0
  24. package/dist/hash/fnv1a.d.ts +11 -0
  25. package/{types → dist}/schema.d.ts +49 -38
  26. package/{types → dist}/ts.d.ts +4 -0
  27. package/dist/webcrypto.d.ts +3 -0
  28. package/package.json +148 -82
  29. package/src/bin/0serve.js +11 -3
  30. package/src/broadcastchannel.js +6 -1
  31. package/src/buffer.js +35 -25
  32. package/src/decoding.js +1 -1
  33. package/src/delta/delta.js +2531 -564
  34. package/src/delta/position.js +165 -0
  35. package/src/delta/rdt/delta.js +105 -0
  36. package/src/delta/rdt/dom.js +307 -0
  37. package/src/delta/rdt.js +255 -0
  38. package/src/delta/transformer/attr.js +126 -0
  39. package/src/delta/transformer/attribution-to-format.js +335 -0
  40. package/src/delta/transformer/children.js +219 -0
  41. package/src/delta/transformer/conform.js +401 -0
  42. package/src/delta/transformer/core.js +323 -0
  43. package/src/delta/transformer/full-attributions.js +282 -0
  44. package/src/delta/transformer/id.js +13 -0
  45. package/src/delta/transformer/inline.js +818 -0
  46. package/src/delta/transformer/pipe.js +272 -0
  47. package/src/delta/transformer/project.js +526 -0
  48. package/src/delta/transformer/rename-attrs.js +152 -0
  49. package/src/delta/transformer/rename.js +84 -0
  50. package/src/delta/transformer/value.js +214 -0
  51. package/src/delta/transformer.js +38 -0
  52. package/src/diff/patience.js +22 -11
  53. package/src/dom.js +2 -2
  54. package/src/encoding.js +1 -1
  55. package/src/environment.js +15 -0
  56. package/src/hash/fnv1a.js +82 -0
  57. package/src/number.js +1 -3
  58. package/src/performance.node.js +3 -3
  59. package/src/schema.js +231 -177
  60. package/src/string.js +3 -5
  61. package/src/testing.js +1 -1
  62. package/src/trait/equality.js +1 -1
  63. package/src/trait/fingerprint.js +1 -1
  64. package/src/ts.js +11 -0
  65. package/src/bin/gendocs.js +0 -117
  66. package/src/component.js +0 -414
  67. package/src/delta/binding.js +0 -372
  68. package/src/tree.js +0 -546
  69. package/types/bin/gendocs.d.ts +0 -3
  70. package/types/component.d.ts +0 -86
  71. package/types/delta/binding.d.ts +0 -106
  72. package/types/diff/patience.d.ts +0 -16
  73. package/types/tree.d.ts +0 -96
  74. package/types/webcrypto.d.ts +0 -3
  75. /package/{types → dist}/array.d.ts +0 -0
  76. /package/{types → dist}/bin/0ecdsa-generate-keypair.d.ts +0 -0
  77. /package/{types → dist}/bin/0serve.d.ts +0 -0
  78. /package/{types → dist}/bin/gentesthtml.d.ts +0 -0
  79. /package/{types → dist}/binary.d.ts +0 -0
  80. /package/{types → dist}/broadcastchannel.d.ts +0 -0
  81. /package/{types → dist}/cache.d.ts +0 -0
  82. /package/{types → dist}/conditions.d.ts +0 -0
  83. /package/{types → dist}/crypto/aes-gcm.d.ts +0 -0
  84. /package/{types → dist}/crypto/common.d.ts +0 -0
  85. /package/{types → dist}/crypto/ecdsa.d.ts +0 -0
  86. /package/{types → dist}/crypto/jwt.d.ts +0 -0
  87. /package/{types → dist}/crypto/rsa-oaep.d.ts +0 -0
  88. /package/{types → dist}/decoding.d.ts +0 -0
  89. /package/{types → dist}/diff.d.ts +0 -0
  90. /package/{types → dist}/dom.d.ts +0 -0
  91. /package/{types → dist}/encoding.d.ts +0 -0
  92. /package/{types → dist}/error.d.ts +0 -0
  93. /package/{types → dist}/eventloop.d.ts +0 -0
  94. /package/{types → dist}/function.d.ts +0 -0
  95. /package/{types → dist}/hash/rabin-gf2-polynomial.d.ts +0 -0
  96. /package/{types → dist}/hash/rabin-uncached.d.ts +0 -0
  97. /package/{types → dist}/hash/rabin.d.ts +0 -0
  98. /package/{types → dist}/hash/sha256.d.ts +0 -0
  99. /package/{types → dist}/hash/sha256.node.d.ts +0 -0
  100. /package/{types → dist}/indexeddb.d.ts +0 -0
  101. /package/{types → dist}/indexeddbV2.d.ts +0 -0
  102. /package/{types → dist}/iterator.d.ts +0 -0
  103. /package/{types → dist}/json.d.ts +0 -0
  104. /package/{types → dist}/list.d.ts +0 -0
  105. /package/{types → dist}/logging.common.d.ts +0 -0
  106. /package/{types → dist}/logging.d.ts +0 -0
  107. /package/{types → dist}/logging.node.d.ts +0 -0
  108. /package/{types → dist}/map.d.ts +0 -0
  109. /package/{types → dist}/math.d.ts +0 -0
  110. /package/{types → dist}/metric.d.ts +0 -0
  111. /package/{types → dist}/mutex.d.ts +0 -0
  112. /package/{types → dist}/number.d.ts +0 -0
  113. /package/{types → dist}/object.d.ts +0 -0
  114. /package/{types → dist}/observable.d.ts +0 -0
  115. /package/{types → dist}/pair.d.ts +0 -0
  116. /package/{types → dist}/performance.d.ts +0 -0
  117. /package/{types → dist}/performance.node.d.ts +0 -0
  118. /package/{types → dist}/pledge.d.ts +0 -0
  119. /package/{types → dist}/prng/Mt19937.d.ts +0 -0
  120. /package/{types → dist}/prng/Xoroshiro128plus.d.ts +0 -0
  121. /package/{types → dist}/prng/Xorshift32.d.ts +0 -0
  122. /package/{types → dist}/prng.d.ts +0 -0
  123. /package/{types → dist}/promise.d.ts +0 -0
  124. /package/{types → dist}/queue.d.ts +0 -0
  125. /package/{types → dist}/random.d.ts +0 -0
  126. /package/{types → dist}/set.d.ts +0 -0
  127. /package/{types → dist}/sort.d.ts +0 -0
  128. /package/{types → dist}/statistics.d.ts +0 -0
  129. /package/{types → dist}/storage.d.ts +0 -0
  130. /package/{types → dist}/string.d.ts +0 -0
  131. /package/{types → dist}/symbol.d.ts +0 -0
  132. /package/{types → dist}/testing.d.ts +0 -0
  133. /package/{types → dist}/time.d.ts +0 -0
  134. /package/{types → dist}/trait/equality.d.ts +0 -0
  135. /package/{types → dist}/trait/fingerprint.d.ts +0 -0
  136. /package/{types → dist}/trait/traits.d.ts +0 -0
  137. /package/{types → dist}/url.d.ts +0 -0
  138. /package/{types → dist}/webcrypto.deno.d.ts +0 -0
  139. /package/{types → dist}/webcrypto.node.d.ts +0 -0
  140. /package/{types → dist}/webcrypto.react-native.d.ts +0 -0
  141. /package/{types → dist}/websocket.d.ts +0 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * # Replicated Data Types (RDTs) and bindings
3
+ *
4
+ * An **RDT** ("replicated data type") is any object that represents some state and communicates changes
5
+ * to that state as {@link import('./delta.js').Delta deltas}. Every RDT is an
6
+ * {@link import('../observable.js').ObservableV2 observable}:
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.
10
+ *
11
+ * Because the changes are deltas, two different representations of "the same" data can be kept in sync
12
+ * even when their shapes differ. That is the job of a {@link Binding}: it connects two RDTs `a` and `b`
13
+ * through a {@link dt.Template transformer template}. The template is instantiated once (against `a`'s
14
+ * schema) into a stateful {@link dt.Transformer}, and every change is routed through it:
15
+ *
16
+ * - a change on `a` is fed to `transformer.applyA(d)`, whose result `{ a, b }` is pushed back via
17
+ * `a.applyDelta` (self-heal) and `b.applyDelta` (the mapped change), and symmetrically for `b` via `applyB`.
18
+ *
19
+ * Feeding the mapped change back into the other RDT makes it emit its own `'delta'`, which would loop
20
+ * forever; a {@link mux mutex} shared per side breaks that echo so a change is only transformed once.
21
+ *
22
+ * Applying a mapped change can make the receiving RDT enforce its own invariants and return a **fix**
23
+ * (see {@link RDT}) — a further change it made to itself. Because the echo mutex swallows the RDT's
24
+ * re-emit during propagation, the binding reads that fix from `applyDelta`'s return value and feeds it
25
+ * back as a fresh change on its side. A fix on one side and a fix on the other are concurrent, so they
26
+ * are rebased against each other: the pending pair `{ a, b }` is run through `transformer.apply`, whose
27
+ * machinery already transposes the two sides. This repeats until neither side reports a fix, so fixes
28
+ * must converge to a fixpoint (a well-behaved RDT applies them idempotently).
29
+ *
30
+ * On creation a binding first **synchronizes the initial state**: `a`'s current state (`a.delta`)
31
+ * is projected through the transformer, and the projection is diffed against `b`'s current state
32
+ * (`b.delta`); the resulting difference is applied to `b` so it matches `a`'s projection (and any
33
+ * self-heal correction is applied back to `a`). `a` is the source of truth — pre-existing state on `b`
34
+ * that `a` does not project to is overwritten.
35
+ *
36
+ * Two reference RDTs live in `./rdt/`:
37
+ * - `./rdt/delta.js` ({@link RDT}) — an in-memory delta whose state is just the accumulated delta.
38
+ * - `./rdt/dom.js` — a live DOM subtree, observed with a `MutationObserver`, that turns DOM mutations
39
+ * into deltas and applies incoming deltas back onto the DOM.
40
+ *
41
+ * @module delta/rdt
42
+ */
43
+
44
+ import * as dt from './transformer.js'
45
+ import * as delta from './delta.js'
46
+ import * as mux from '../mutex.js'
47
+ import * as s from '../schema.js'
48
+
49
+ // Re-export the two reference RDTs this module's doc references, so `bind`, `deltaRDT` and `domRDT` are
50
+ // reachable from one import. The leaf modules (`lib0/delta/rdt/delta`, `lib0/delta/rdt/dom`) stay
51
+ // independently importable, so a consumer who wants only one keeps it tree-shakeable.
52
+ export { deltaRDT } from './rdt/delta.js'
53
+ export { $domDelta, domRDT } from './rdt/dom.js'
54
+
55
+ /**
56
+ * @template T
57
+ * @typedef {import('../schema.js').Schema<T>} Schema
58
+ */
59
+
60
+ /**
61
+ * Abstract interface for a delta-based Replicated Data Type.
62
+ *
63
+ * An RDT is an observable that emits a `'delta'` (and, on teardown, a `'destroy'`) event, exposes the
64
+ * {@link Schema schema} of the deltas it produces via `$delta` (so a {@link Binding} can initialize a
65
+ * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign
66
+ * deltas via `applyDelta`.
67
+ *
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
72
+ * (`d` together with the fix) on the `'delta'` channel.
73
+ *
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,
107
+ * destroy: () => void
108
+ * }} RDT
109
+ */
110
+
111
+ /**
112
+ * Schema guard for any {@link RDT}. RDTs are **duck-typed** — they have several independent
113
+ * implementations (`deltaRDT`, `domRDT`, …) and no common constructor — so this matches by shape
114
+ * rather than by `instanceof`: an object exposing a `$delta` {@link Schema}, an `applyDelta` method,
115
+ * and the observable `on`/`destroy` channel. The (possibly expensive) `delta` getter is not invoked.
116
+ *
117
+ * @type {Schema<RDT<any>>}
118
+ */
119
+ export const $rdt = /** @type {Schema<RDT<any>>} */ (/* @__PURE__ */ s.$custom(o =>
120
+ o != null &&
121
+ s.$$schema.check(o.$delta) &&
122
+ typeof o.applyDelta === 'function' &&
123
+ typeof o.on === 'function' &&
124
+ typeof o.destroy === 'function'
125
+ ))
126
+
127
+ /**
128
+ * Propagate a pair of concurrent changes between the two sides of `binding` until they converge.
129
+ *
130
+ * `ta` / `tb` are changes that have ALREADY been applied to `a` / `b` (an incoming `'delta'`, or a fix
131
+ * returned by a previous `applyDelta`). Each is mapped onto the other side via the transformer —
132
+ * `apply` rebases the two against each other — and the mapped results are applied, which may yield
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`
146
+ */
147
+ const propagate = (binding, ta, tb) => {
148
+ while ((ta != null && !ta.isEmpty()) || (tb != null && !tb.isEmpty())) {
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
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @typedef {object} BindOptions
160
+ * @property {delta.DiffOptions['compare']} [diffCompare] **Experimental** (may be changed or removed
161
+ * in a future release). Forwarded as `compare` to {@link delta.diff} when the initial-state sync diffs
162
+ * `a`'s projection against `b`'s current state, controlling how nodes are paired into `modify` ops.
163
+ */
164
+
165
+ /**
166
+ * Connects two RDTs so that changes on either side are transformed and reflected on the other.
167
+ *
168
+ * The {@link dt.Transformer transformer} it drives **manipulates deltas in place** (it splices op lists
169
+ * and rebases nodes directly, rather than only via `apply`/`rebase`) — a deliberate exception to the
170
+ * general rule that deltas are edited through `apply`/`rebase`. Consequently every change handed to it
171
+ * must be a privately-owned, fully-mutable delta, so the binding {@link delta.cloneDeep deep-clones}
172
+ * each incoming change (which is a shared read delta — an event payload or a snapshot) before the
173
+ * transformer touches it. See the initial-state sync below and {@link propagate}.
174
+ *
175
+ * @template {delta.DeltaConf} A
176
+ * @template {delta.DeltaConf} B
177
+ */
178
+ export class Binding {
179
+ /**
180
+ * @param {RDT<A>} a
181
+ * @param {RDT<B>} b
182
+ * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory; defaults to the
183
+ * {@link dt.id identity} transformer. `bind` injects `a`'s schema, then materializes via `.init()`.
184
+ * @param {BindOptions} [options]
185
+ */
186
+ constructor (a, b, template = /** @type {dt.TemplateFactory<A,B>} */ (dt.id), options = {}) {
187
+ /**
188
+ * @type {dt.Transformer<A,B>}
189
+ */
190
+ this.t = template(a.$delta).init()
191
+ /**
192
+ * @type {RDT<A>}
193
+ */
194
+ this.a = a
195
+ /**
196
+ * @type {RDT<B>}
197
+ */
198
+ this.b = b
199
+ this._mux = mux.createMutex()
200
+ this._achanged = this.a.on('delta', d => this._mux(() => propagate(this, d, null)))
201
+ this._bchanged = this.b.on('delta', d => this._mux(() => propagate(this, null, d)))
202
+ this.a.on('destroy', this.destroy)
203
+ this.b.on('destroy', this.destroy)
204
+ // Sync the initial state. `a` is the source of truth: project its current state through the
205
+ // transformer (`applyA`) — which also yields any self-heal correction for `a` (`tres.a`) — then
206
+ // diff the projection against `b`'s current state and apply the difference so `b` ends up
207
+ // matching `a`'s projection. Any fixes the two sides report are reconciled via `propagate`.
208
+ // NOTE: `delta.diff` is content-only (it excludes marks), so cursor marks present on `a` at bind
209
+ // time are NOT transferred to `b` here; marks ride only on subsequent live `applyA`/`applyB`.
210
+ // Wrapped in the mutex so these `applyDelta` calls don't echo back through the listeners above.
211
+ this._mux(() => {
212
+ // `a.delta` is a shared read snapshot (for an in-memory RDT it IS the live state, whose nested
213
+ // children are frozen `done`), so DEEP-clone it into a private, fully-mutable builder before the
214
+ // transformer — which rebases its input in place — projects it. A shallow `clone` would still share
215
+ // those frozen children and corrupt / fail on the live state (see `delta.cloneDeep`).
216
+ const tres = this.t.applyA(delta.cloneDeep(this.a.delta))
217
+ const fa = tres.a ? this.a.applyDelta(tres.a, this) : null
218
+ // `diff` shares nested children with `tres.b` by default, and `tres.b` is a (possibly stateful)
219
+ // transformer's output that may still alias its internal state — so `clone` keeps the diff
220
+ // independent, since it is then applied into `b` (which freezes it) and propagated onward.
221
+ const diffB = tres.b ? delta.diff(this.b.delta, tres.b, { clone: true, compare: options.diffCompare }) : null
222
+ const fb = diffB ? this.b.applyDelta(diffB, this) : null
223
+ propagate(this, fa, fb)
224
+ })
225
+ }
226
+
227
+ /**
228
+ * Tear the binding down: unsubscribe both sides' `'delta'` and `'destroy'` listeners so changes are no
229
+ * longer propagated. Call this when you no longer need the two RDTs kept in sync. It is also invoked
230
+ * automatically when either bound RDT emits `'destroy'`. The RDTs themselves are NOT destroyed.
231
+ */
232
+ destroy = () => {
233
+ this.a.off('destroy', this.destroy)
234
+ this.b.off('destroy', this.destroy)
235
+ this.a.off('delta', this._achanged)
236
+ this.b.off('delta', this._bchanged)
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Connect two RDTs through a transformer template. Changes on `a` are mapped onto `b` and vice versa.
242
+ * Without a `template` the {@link dt.id identity} transformer is used, keeping both sides equal. Call
243
+ * {@link Binding#destroy} on the returned binding to stop syncing (it also self-destroys when either RDT
244
+ * emits `'destroy'`).
245
+ *
246
+ * @template {delta.DeltaConf} A
247
+ * @template {delta.DeltaConf} B
248
+ * @param {RDT<A>} a
249
+ * @param {RDT<B>} b
250
+ * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory (e.g. `dt.id` or
251
+ * `$d => dt.renameAttrs($d, {a:'b'})`)
252
+ * @param {BindOptions} [options] `diffCompare` is **experimental** — see {@link BindOptions}.
253
+ * @return {Binding<A,B>}
254
+ */
255
+ export const bind = (a, b, template, options = {}) => new Binding(a, b, template, options)
@@ -0,0 +1,126 @@
1
+ import * as delta from '../delta.js'
2
+ import * as s from '../../schema.js'
3
+ import { Transformer, Template, createTransformResult, attrsShapeOf } from './core.js'
4
+
5
+ /**
6
+ * @template {string} AttrName
7
+ * @template {delta.DeltaConf} IN
8
+ * @typedef {import('./core.js').ResolveOut<{ name: 'lib0:value', attrs: { value: IN extends { attrs: { [K in AttrName]: infer V } } ? V : never }}>} ApplyAttr
9
+ */
10
+
11
+ /**
12
+ * @param {delta.DeltaBuilderAny} outDelta
13
+ * @param {string|number} from
14
+ * @param {string|number} to
15
+ * @param {delta.DeltaAny} inDelta
16
+ */
17
+ const attrTransformHelper = (outDelta, from, to, inDelta) => {
18
+ const attrOp = inDelta.attrs[from]
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
25
+ const oattrs = /** @type {any} */ (outDelta.attrs)
26
+ oattrs[to] = c
27
+ }
28
+ // Move a mark on the projected attribute (`from` -> `to`); drop root marks on any other attribute or
29
+ // content (only this one attribute exists on the other side). A mark *inside* the attribute value
30
+ // rides on the cloned attr op above, and a mark *delete* (id-keyed) rides verbatim via mergeRootMarks.
31
+ delta.mergeRootMarks(outDelta, inDelta, k => k === from ? to : null)
32
+ // flag conservatively: the direct attr assignment above bypasses the builder, so a mark inside the
33
+ // cloned attribute value would otherwise not set the flag (marksToPositions self-corrects a false +ve)
34
+ outDelta.maybeHasMarks ||= inDelta.maybeHasMarks
35
+ return outDelta
36
+ }
37
+
38
+ /**
39
+ * Projects a single node attribute into a `lib0:value` node's `value` attribute (and back).
40
+ *
41
+ * @template {string} AttrName
42
+ * @template {delta.DeltaConf} [IN=any]
43
+ * @extends {Template<IN, ApplyAttr<AttrName, IN>>}
44
+ */
45
+ export class Attr extends Template {
46
+ /**
47
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
48
+ * @param {AttrName} attrName
49
+ */
50
+ constructor ($d, attrName) {
51
+ const m = attrsShapeOf($d)
52
+ const $val = (m && m[attrName]) || s.$any
53
+ super($d, /** @type {any} */ (delta.$delta('lib0:value', { attrs: { value: $val } })))
54
+ this.attrName = attrName
55
+ }
56
+
57
+ get name () { return 'lib0:attr:' + this.attrName }
58
+
59
+ /**
60
+ * @return {Transformer<IN, ApplyAttr<AttrName, IN>>}
61
+ */
62
+ init () {
63
+ return /** @type {any} */ (new AttrTransformer(this.$in, this.$out, this.attrName))
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @template {delta.DeltaConf} A
69
+ * @template {delta.DeltaConf} B
70
+ * @extends {Transformer<A,B>}
71
+ */
72
+ export class AttrTransformer extends Transformer {
73
+ /**
74
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
75
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
76
+ * @param {string} attrName
77
+ */
78
+ constructor ($in, $out, attrName) {
79
+ super($in, $out)
80
+ this.attrName = /** @type {keyof delta.DeltaConfGetAttrs<A> & (string|number)} */ (attrName)
81
+ }
82
+
83
+ /**
84
+ * @param {delta.DeltaBuilder<A>} d
85
+ * @return {import('./core.js').TransformResultAny}
86
+ */
87
+ applyA (d) {
88
+ return createTransformResult(
89
+ null,
90
+ attrTransformHelper(
91
+ delta.create('lib0:value'),
92
+ this.attrName,
93
+ 'value',
94
+ d
95
+ )
96
+ )
97
+ }
98
+
99
+ /**
100
+ * @param {delta.DeltaBuilder<B>} d
101
+ * @return {import('./core.js').TransformResultAny}
102
+ */
103
+ applyB (d) {
104
+ return createTransformResult(
105
+ attrTransformHelper(
106
+ delta.create(),
107
+ 'value',
108
+ this.attrName,
109
+ d
110
+ ),
111
+ null
112
+ )
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Project a single node attribute `attrName` into a `lib0:value` carrier (and back). Returns a
118
+ * reusable {@link Attr} template (a `project` hole, or `.init()` for a standalone transformer).
119
+ *
120
+ * @template {string} AttrName
121
+ * @template {delta.DeltaConf} IN
122
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
123
+ * @param {AttrName} attrName
124
+ * @return {Attr<AttrName, IN>}
125
+ */
126
+ export const attr = ($d, attrName) => new Attr($d, attrName)