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,335 @@
1
+ import * as delta from '../delta.js'
2
+ import * as s from '../../schema.js'
3
+ import * as object from '../../object.js'
4
+ import { Transformer, Template, createTransformResult } from './core.js'
5
+
6
+ /**
7
+ * # `attributionToFormat`
8
+ *
9
+ * Render a delta's **`attribution`** dimension (provenance — who inserted/deleted/formatted) into a
10
+ * reserved `y-attributed-*` slice of the **`format`** dimension, so a rich-text editor can display
11
+ * provenance as decorations. The reverse direction strips the decorations back out.
12
+ *
13
+ * Forward (`applyA`, data → view), per content op:
14
+ * - `attribution` whose keys start with `insert`/`delete`/`format` ⇒ `format['y-attributed-insert' |
15
+ * 'y-attributed-delete' | 'y-attributed-format']`, computed by the matching `conf` handler.
16
+ *
17
+ * A node's attribute ops carry attribution but have **no format slot**, and a node has no format of
18
+ * its own — its format lives on the *parent* op that holds it. So a node-attr's attribution is lifted
19
+ * to that parent (`insert`/`modify`) op as `format['y-attributed-attrs'] = { <attrKey>: <value> }`,
20
+ * observed at the boundary before recursing into the child ("the parent formats the child").
21
+ *
22
+ * Reverse (`applyB`, view → data) is a pure strip: every `y-attributed-*` format key is removed and
23
+ * everything else passes through. *The view never attributes.*
24
+ *
25
+ * The transform is **structure-preserving** (1:1 positions) and **stateless** — it walks each change
26
+ * op-by-op and recurses through the whole nested delta in one pass (no per-child transformer
27
+ * instances). See {@link attributionToFormat}.
28
+ *
29
+ * @module delta/transformer/attribution-to-format
30
+ */
31
+
32
+ // Reserved, fixed format-key namespace. Every key produced here starts with `Y_PREFIX`, which is also
33
+ // how `applyB` recognises (and strips) them.
34
+ const Y_PREFIX = 'y-attributed-'
35
+ const Y_ATTRS = 'y-attributed-attrs'
36
+
37
+ /** Content-attribution dimension → its namespaced format key. */
38
+ const CONTENT_KEYS = { insert: 'y-attributed-insert', delete: 'y-attributed-delete', format: 'y-attributed-format' }
39
+ const CONTENT_DIMS = /** @type {Array<'insert'|'delete'|'format'>} */ (object.keys(CONTENT_KEYS))
40
+
41
+ /**
42
+ * Maps an attribution object to the format value placed under its namespaced key (or `null` to clear
43
+ * that key, `{}`/`undefined` to leave it unchanged).
44
+ *
45
+ * @typedef {(attribution: import('../delta.js').Attribution) => any} Handler
46
+ */
47
+
48
+ /**
49
+ * Per-dimension handlers. Each fires when the op's attribution has a key with that dimension's prefix
50
+ * (`insert`/`insertAt` → `insert`, etc.); the whole attribution object is passed in. A missing handler
51
+ * means "do not render that dimension".
52
+ *
53
+ * @typedef {{ insert?: Handler, delete?: Handler, format?: Handler, attrs?: Handler }} Conf
54
+ */
55
+
56
+ /**
57
+ * A handler result that means "no change" (skip): `undefined` or an empty plain object `{}`. A bare
58
+ * scalar (string/number) or a non-empty object is a real value; `null` is handled separately (clear).
59
+ *
60
+ * @param {any} r
61
+ * @return {boolean}
62
+ */
63
+ const isSkip = (r) => r === undefined || (s.$objectAny.check(r) && object.isEmpty(r))
64
+
65
+ /**
66
+ * Build the `y-attributed-*` content-format increment from a content op's `attribution`. `isData`
67
+ * distinguishes a settled data op (`insert`/`text`: `attribution` is an object or `null`=none) from an
68
+ * instruction op (`retain`/`modify`: `undefined`=skip, `null`=clear-all).
69
+ *
70
+ * @param {Conf} conf
71
+ * @param {import('../delta.js').Attribution|null|undefined} attribution
72
+ * @param {boolean} isData
73
+ * @return {{[k:string]:any}|undefined}
74
+ */
75
+ const contentFmt = (conf, attribution, isData) => {
76
+ if (attribution === undefined) return undefined
77
+ /** @type {{[k:string]:any}} */
78
+ const f = {}
79
+ if (attribution === null) {
80
+ if (isData) return undefined // data "none" — nothing to render
81
+ for (const dim of CONTENT_DIMS) if (conf[dim] != null) f[CONTENT_KEYS[dim]] = null // instruction clear-all
82
+ return object.isEmpty(f) ? undefined : f
83
+ }
84
+ const ks = object.keys(attribution)
85
+ for (const dim of CONTENT_DIMS) {
86
+ const handler = conf[dim]
87
+ if (handler != null && ks.some(k => k.startsWith(dim))) {
88
+ const r = handler(attribution)
89
+ // `null` ⇒ clear the key, but only as an *instruction* — a data op stores resolved formats with
90
+ // no `{k:null}` leaves, so on a data op a `null` result is "nothing to render" (skip).
91
+ if (r === null) { if (!isData) f[CONTENT_KEYS[dim]] = null } else if (!isSkip(r)) f[CONTENT_KEYS[dim]] = r
92
+ }
93
+ }
94
+ return object.isEmpty(f) ? undefined : f
95
+ }
96
+
97
+ /**
98
+ * Build the `y-attributed-attrs` format value from a child node's attribute-op attributions — the
99
+ * lift that lets the *parent* op carry a node's attr provenance (attr ops have no format slot). For a
100
+ * `modifyAttr` op a `null` attribution is an instruction *clear* (⇒ `{ <key>: null }`); for a settled
101
+ * `setAttr`/`deleteAttr` op `null` is "none" (skipped).
102
+ *
103
+ * @param {Conf} conf
104
+ * @param {import('../delta.js').DeltaAny} node
105
+ * @return {{[k:string]:any}|undefined}
106
+ */
107
+ const attrsFmt = (conf, node) => {
108
+ const handler = conf.attrs
109
+ if (handler == null) return undefined
110
+ /** @type {{[k:string]:any}} */
111
+ const map = {}
112
+ for (const op of node.attrs) {
113
+ const isInstr = delta.$modifyAttrOp.check(op) // modifyAttr carries an attribution *instruction*; setAttr/deleteAttr resolved data
114
+ const a = op.attribution
115
+ if (a === undefined) continue // instruction skip
116
+ if (a === null) {
117
+ if (isInstr) map[op.key] = null // instruction clear (data "none" ⇒ skip)
118
+ continue
119
+ }
120
+ const r = handler(a)
121
+ if (r === null) { if (isInstr) map[op.key] = null } else if (!isSkip(r)) map[op.key] = r
122
+ }
123
+ return object.isEmpty(map) ? undefined : { [Y_ATTRS]: map }
124
+ }
125
+
126
+ /**
127
+ * Merge `y-attributed-*` increments onto a content op's existing `format`, preserving real formats
128
+ * (`bold`, …) and the op's tri-state. With no increment, `base` is returned verbatim (so a `null`
129
+ * clear-all or a `{k:null}` removal survives). With increments, a `null`/`undefined` base contributes
130
+ * nothing and only the namespaced keys are emitted.
131
+ *
132
+ * @param {{[k:string]:any}|null|undefined} base op.format (skip `undefined` / clear-all `null` / object)
133
+ * @param {...({[k:string]:any}|undefined)} adds namespaced format increments
134
+ * @return {{[k:string]:any}|null|undefined}
135
+ */
136
+ const combineFmt = (base, ...adds) => {
137
+ const extra = object.assign({}, ...adds)
138
+ if (object.isEmpty(extra)) return base
139
+ return base == null ? extra : object.assign({}, base, extra)
140
+ }
141
+
142
+ /**
143
+ * Remove every reserved `y-attributed-*` key from a `format`. Returns the input unchanged when it has
144
+ * none (preserving `undefined`/`null`/object verbatim), or `undefined` when stripping empties it.
145
+ *
146
+ * @param {{[k:string]:any}|null|undefined} format
147
+ * @return {{[k:string]:any}|null|undefined}
148
+ */
149
+ const stripY = (format) => {
150
+ if (format == null) return format
151
+ /** @type {{[k:string]:any}} */
152
+ const r = {}
153
+ let stripped = false
154
+ for (const k in format) {
155
+ if (k.startsWith(Y_PREFIX)) stripped = true
156
+ else r[k] = format[k]
157
+ }
158
+ return stripped ? (object.isEmpty(r) ? undefined : r) : format
159
+ }
160
+
161
+ /**
162
+ * Re-emit a node's own attribute ops without attribution. The view never attributes — a node's
163
+ * attr-attribution is rendered on the *parent* op (see {@link attrsFmt}) — so it must not also ride on
164
+ * the attr op. Rebuilt via the builder rather than mutating cloned ops.
165
+ *
166
+ * @param {import('../delta.js').DeltaBuilderAny} out
167
+ */
168
+ const stripAttrAttributions = (out) => {
169
+ /** @type {Array<any>} */
170
+ const ops = []
171
+ for (const op of out.attrs) ops.push(op)
172
+ for (const op of ops) {
173
+ if (delta.$modifyAttrOp.check(op)) {
174
+ if (op.attribution !== undefined) out.modifyAttr(op.key, op.value)
175
+ } else if (delta.$setAttrOp.check(op)) {
176
+ if (op.attribution != null) out.setAttr(op.key, op.value)
177
+ } else if (op.attribution != null) { // DeleteAttrOp
178
+ out.deleteAttr(op.key)
179
+ }
180
+ }
181
+ }
182
+
183
+ /**
184
+ * The whole transform, both directions, in one stateless recursive pass over the nested delta.
185
+ *
186
+ * @param {Conf} conf
187
+ * @param {import('../delta.js').DeltaAny} d
188
+ * @param {boolean} fwd `true` maps A→B (attribution→format), `false` maps B→A (strip).
189
+ * @return {import('./core.js').TransformResultAny}
190
+ */
191
+ const transform = (conf, d, fwd) => {
192
+ const out = /** @type {any} */ (delta.cloneShallow(d))
193
+ if (fwd) {
194
+ stripAttrAttributions(out)
195
+ for (const op of d.children) {
196
+ if (delta.$textOp.check(op)) {
197
+ out.insert(op.insert, combineFmt(op.format, contentFmt(conf, op.attribution, true)))
198
+ } else if (delta.$retainOp.check(op)) {
199
+ out.retain(op.retain, combineFmt(op.format, contentFmt(conf, op.attribution, false)))
200
+ } else if (delta.$insertOp.check(op)) {
201
+ // one element at a time: the builder re-coalesces equal formats and splits differing ones, so
202
+ // children needing distinct `y-attributed-attrs` land in their own insert ops automatically
203
+ for (const el of op.insert) {
204
+ if (delta.$deltaAny.check(el)) {
205
+ out.insert([transform(conf, el, true).b], combineFmt(op.format, contentFmt(conf, op.attribution, true), attrsFmt(conf, el)))
206
+ } else {
207
+ out.insert([el], combineFmt(op.format, contentFmt(conf, op.attribution, true)))
208
+ }
209
+ }
210
+ } else if (delta.$deleteOp.check(op)) {
211
+ out.delete(op.delete)
212
+ } else { // $modifyOp
213
+ out.modify(transform(conf, op.value, true).b, combineFmt(op.format, contentFmt(conf, op.attribution, false), attrsFmt(conf, op.value)))
214
+ }
215
+ }
216
+ } else {
217
+ for (const op of d.children) {
218
+ if (delta.$textOp.check(op)) {
219
+ out.insert(op.insert, stripY(op.format))
220
+ } else if (delta.$retainOp.check(op)) {
221
+ out.retain(op.retain, stripY(op.format))
222
+ } else if (delta.$insertOp.check(op)) {
223
+ for (const el of op.insert) {
224
+ out.insert([delta.$deltaAny.check(el) ? transform(conf, el, false).a : el], stripY(op.format))
225
+ }
226
+ } else if (delta.$deleteOp.check(op)) {
227
+ out.delete(op.delete)
228
+ } else { // $modifyOp
229
+ out.modify(transform(conf, op.value, false).a, stripY(op.format))
230
+ }
231
+ }
232
+ }
233
+ out.done(false)
234
+ return fwd ? createTransformResult(null, out) : createTransformResult(out, null)
235
+ }
236
+
237
+ /**
238
+ * Stateless transformer (holds only its `conf`); a single shared instance per template suffices.
239
+ *
240
+ * @extends {Transformer<any,any>}
241
+ */
242
+ export class AttributionToFormatTransformer extends Transformer {
243
+ /**
244
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
245
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
246
+ * @param {Conf} conf
247
+ */
248
+ constructor ($in, $out, conf) {
249
+ super($in, $out)
250
+ this.conf = conf
251
+ }
252
+
253
+ /**
254
+ * @param {delta.DeltaBuilderAny} d
255
+ * @return {import('./core.js').TransformResultAny}
256
+ */
257
+ applyA (d) {
258
+ return transform(this.conf, d, true)
259
+ }
260
+
261
+ /**
262
+ * @param {delta.DeltaBuilderAny} d
263
+ * @return {import('./core.js').TransformResultAny}
264
+ */
265
+ applyB (d) {
266
+ return transform(this.conf, d, false)
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Compute the output schema: widen the input `$formats` to admit the four `y-attributed-*` keys (only
272
+ * text-op formats are schema-checked, delta.js `$Delta#check`). Falls back to `$deltaAny` when `$in`
273
+ * is loose. A local rebuild (not `withAttrs`/`withName`, which hardcode `recursiveChildren:false`) so
274
+ * recursive node trees survive.
275
+ *
276
+ * @param {import('../../schema.js').Schema<delta.DeltaAny>} $in
277
+ * @return {import('../../schema.js').Schema<delta.DeltaAny>}
278
+ */
279
+ const computeOut = ($in) => {
280
+ if (!delta.$$delta.check($in)) return delta.$deltaAny
281
+ const $formats = $in.shape.$formats
282
+ const $wide = s.$$object.check($formats)
283
+ ? s.$object(object.assign({}, $formats.shape, { 'y-attributed-insert': s.$any.optional, 'y-attributed-delete': s.$any.optional, 'y-attributed-format': s.$any.optional, [Y_ATTRS]: s.$any.optional }))
284
+ : $formats // `s.$any` (the default) already admits the namespaced keys
285
+ return new delta.$Delta($in.shape.$name, $in.shape.$attrs, $in.shape.$children, $in.shape.hasText, $in.shape.recursiveChildren, $wide)
286
+ }
287
+
288
+ /**
289
+ * Template for {@link AttributionToFormatTransformer}.
290
+ *
291
+ * @template {delta.DeltaConf} [IN=any]
292
+ * @extends {Template<IN, any>}
293
+ */
294
+ export class AttributionToFormat extends Template {
295
+ /**
296
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
297
+ * @param {Conf} conf
298
+ */
299
+ constructor ($d, conf) {
300
+ super($d, /** @type {any} */ (computeOut($d)))
301
+ /**
302
+ * @type {Conf}
303
+ */
304
+ this.conf = conf
305
+ }
306
+
307
+ get name () { return 'lib0:attributionToFormat' }
308
+
309
+ /**
310
+ * @return {Transformer<IN, any>}
311
+ */
312
+ init () {
313
+ return new AttributionToFormatTransformer(this.$in, this.$out, this.conf)
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Render a delta's `attribution` dimension into a reserved `y-attributed-*` format namespace (forward)
319
+ * and strip it back out (reverse) — see the {@link module:delta/transformer/attribution-to-format
320
+ * module doc}. `conf` supplies per-dimension handlers (`insert`/`delete`/`format` for content ops,
321
+ * `attrs` for node-attribute provenance); each maps the op's attribution to the value placed under the
322
+ * namespaced format key (`null` clears it, `{}`/`undefined` leaves it unchanged).
323
+ *
324
+ * The transformer assumes every attribution-bearing op carries the **complete** attribution: the
325
+ * format dimension merges flat/wholesale, so it cannot replicate `attribution.format`'s deep merge,
326
+ * and an incremental update would overwrite a whole `y-attributed-attrs` map or `y-attributed-format`
327
+ * object. Returns a reusable {@link AttributionToFormat} template (`.init()` for a standalone
328
+ * transformer).
329
+ *
330
+ * @template {delta.DeltaConf} IN
331
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
332
+ * @param {Conf} conf
333
+ * @return {AttributionToFormat<IN>}
334
+ */
335
+ export const attributionToFormat = ($d, conf) => new AttributionToFormat($d, conf)
@@ -0,0 +1,219 @@
1
+ import * as delta from '../delta.js'
2
+ import * as math from '../../math.js'
3
+ import { Transformer, Template, createTransformResult } from './core.js'
4
+
5
+ /**
6
+ * Stateful transformer that descends one level into a node's child *nodes* and applies a per-child
7
+ * sub-transformer, chosen by `handler`. Attributes and text pass through untouched. Designed to be
8
+ * applied recursively (see {@link children}).
9
+ *
10
+ * The per-child sub-transformer instances are kept in {@link ChildrenTransformer#childTs}, positionally
11
+ * aligned to the parent's child list (children carry no stable id - identity is positional). `children`
12
+ * never changes the child count at its own level, so side A and side B stay aligned position-for-
13
+ * position and the same `childTs` indexes both directions.
14
+ *
15
+ * `childTs` is a sparse positional map kept as a delta: each transformed child is an `insert([t])` op
16
+ * holding its sub-transformer; every other position (text runs, opted-out nodes) is a coalesced
17
+ * `retain(n)` gap. A run of N text characters costs one retain op, not N array slots. It is walked with
18
+ * a forward cursor in step with the change (see {@link ChildrenTransformer#transform}). Transformer
19
+ * instances are carried by reference across rebuilds, so their per-instance state survives.
20
+ *
21
+ * @extends {Transformer<any,any>}
22
+ */
23
+ export class ChildrenTransformer extends Transformer {
24
+ /**
25
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
26
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
27
+ * @param {(child: delta.DeltaAny, $child: import('../../schema.js').Schema<delta.DeltaAny>) => Template | null} handler
28
+ * picks the (schema-bound) template for a child node, or `null` to leave it untransformed. Evaluated
29
+ * once, when the child is inserted, with the derived per-child schema.
30
+ * @param {import('../../schema.js').Schema<delta.DeltaAny>} childSchema per-child-node schema each
31
+ * sub-template is built against (derived from the parent's input schema; `$deltaAny` when not derivable).
32
+ */
33
+ constructor ($in, $out, handler, childSchema) {
34
+ super($in, $out)
35
+ this.handler = handler
36
+ /**
37
+ * Sparse positional map of sub-transformers: `insert([t])` at each transformed child, `retain(n)`
38
+ * over gaps (text and opted-out nodes).
39
+ *
40
+ * @type {delta.DeltaBuilderAny}
41
+ */
42
+ this.childTs = delta.create()
43
+ this.childSchema = childSchema
44
+ }
45
+
46
+ /**
47
+ * @param {delta.DeltaBuilderAny} d the change to map
48
+ * @param {boolean} fwd `true` maps side A -> B (applying sub-transformers' `applyA`), `false` maps
49
+ * side B -> A. `children` is 1:1 over children, so the same `childTs` indexes both directions.
50
+ * @return {import('./core.js').TransformResultAny}
51
+ */
52
+ transform (d, fwd) {
53
+ // `cloneShallow` carries the node's name, attribute ops, and own (root) marks; only the child nodes
54
+ // are (re)built below, and nested children's marks ride on those rebuilt children. It is the shared
55
+ // mark-carrying primitive, so this node's marks cannot be silently dropped. `any` keeps `out` within
56
+ // the shared TS instantiation budget (see {@link import('./inline.js')}).
57
+ const out = /** @type {any} */ (delta.cloneShallow(d))
58
+ // Walk the old sparse map with a forward cursor while rebuilding a fresh one. The cursor tracks an
59
+ // old-side child position (list node + offset within it).
60
+ const newTs = delta.create()
61
+ let srcNode = this.childTs.children.start
62
+ let srcOff = 0
63
+ /**
64
+ * Copy `n` positions from the old map to `newTs`, advancing the cursor. Transformer references are
65
+ * preserved (the array slice is shallow), so their per-instance state survives the rebuild.
66
+ *
67
+ * @param {number} n
68
+ */
69
+ const carry = n => {
70
+ let rem = n
71
+ while (rem > 0 && srcNode != null) {
72
+ const take = math.min(srcNode.length - srcOff, rem)
73
+ if (delta.$insertOp.check(srcNode)) {
74
+ newTs.insert(srcNode.insert.slice(srcOff, srcOff + take))
75
+ } else {
76
+ newTs.retain(take)
77
+ }
78
+ srcOff += take
79
+ rem -= take
80
+ if (srcOff >= srcNode.length) { srcNode = srcNode.next; srcOff = 0 }
81
+ }
82
+ }
83
+ /**
84
+ * Advance the cursor `n` positions without copying (a delete drops those transformers).
85
+ *
86
+ * @param {number} n
87
+ */
88
+ const drop = n => {
89
+ let rem = n
90
+ while (rem > 0 && srcNode != null) {
91
+ const take = math.min(srcNode.length - srcOff, rem)
92
+ srcOff += take
93
+ rem -= take
94
+ if (srcOff >= srcNode.length) { srcNode = srcNode.next; srcOff = 0 }
95
+ }
96
+ }
97
+ // the sub-transformer at the cursor, or null for a gap (text / opted-out node)
98
+ const peek = () => (srcNode != null && delta.$insertOp.check(srcNode)) ? srcNode.insert[srcOff] : null
99
+ for (const op of d.children) {
100
+ if (delta.$retainOp.check(op)) {
101
+ out.retain(op.retain, op.format, op.attribution)
102
+ carry(op.retain)
103
+ } else if (delta.$textOp.check(op)) {
104
+ out.insert(op.insert, op.format, op.attribution)
105
+ newTs.retain(op.insert.length) // text is a gap - no sub-transformer
106
+ } else if (delta.$insertOp.check(op)) {
107
+ for (const el of op.insert) {
108
+ const tmpl = delta.$deltaAny.check(el) ? this.handler(el, this.childSchema) : null
109
+ if (tmpl != null) {
110
+ const t = tmpl.init()
111
+ const r = fwd ? t.applyA(el) : t.applyB(el)
112
+ out.insert([fwd ? r.b : r.a], op.format, op.attribution)
113
+ newTs.insert([t])
114
+ } else {
115
+ out.insert([el], op.format, op.attribution)
116
+ newTs.retain(1) // opted-out node is a gap
117
+ }
118
+ }
119
+ } else if (delta.$deleteOp.check(op)) {
120
+ out.delete(op.delete)
121
+ drop(op.delete)
122
+ } else if (delta.$modifyOp.check(op)) {
123
+ const t = peek()
124
+ if (t != null) {
125
+ // op.value is an immutable Delta view; transformers consume a DeltaBuilder, so clone it
126
+ const r = fwd ? t.applyA(delta.clone(op.value)) : t.applyB(delta.clone(op.value))
127
+ out.modify(fwd ? r.b : r.a, op.format, op.attribution)
128
+ } else {
129
+ out.modify(delta.clone(op.value), op.format, op.attribution)
130
+ }
131
+ carry(1)
132
+ }
133
+ }
134
+ // the change implicitly retains everything after its last op - carry those untouched positions'
135
+ // sub-transformers into the rebuilt map (out, being a change, needs no trailing retain)
136
+ carry(Infinity)
137
+ out.done(false)
138
+ this.childTs = newTs
139
+ return fwd ? createTransformResult(null, out) : createTransformResult(out, null)
140
+ }
141
+
142
+ /**
143
+ * @param {delta.DeltaBuilderAny} da
144
+ */
145
+ applyA (da) {
146
+ return this.transform(da, true)
147
+ }
148
+
149
+ /**
150
+ * @param {delta.DeltaBuilderAny} db
151
+ */
152
+ applyB (db) {
153
+ return this.transform(db, false)
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Derive the per-child-node schema from the parent's input schema, so each child's sub-transformer is
159
+ * built against a concrete schema instead of `$deltaAny`. Falls back to `$deltaAny`.
160
+ *
161
+ * @param {import('../../schema.js').Schema<delta.DeltaAny>} $in
162
+ * @return {import('../../schema.js').Schema<delta.DeltaAny>}
163
+ */
164
+ const childSchemaOf = ($in) => {
165
+ if (!delta.$$delta.check($in)) return delta.$deltaAny
166
+ if ($in.shape.recursiveChildren) return $in
167
+ const $ch = $in.shape.$children
168
+ return delta.$$delta.check($ch) ? $ch : delta.$deltaAny
169
+ }
170
+
171
+ /**
172
+ * Template for {@link ChildrenTransformer}.
173
+ *
174
+ * @template {delta.DeltaConf} [IN=any]
175
+ * @extends {Template<IN, any>}
176
+ */
177
+ export class Children extends Template {
178
+ /**
179
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
180
+ * @param {(child: delta.DeltaAny, $child: import('../../schema.js').Schema<delta.DeltaAny>) => Template | null} handler
181
+ */
182
+ constructor ($d, handler) {
183
+ super($d, /** @type {any} */ (delta.$deltaAny))
184
+ this.handler = handler
185
+ /**
186
+ * @type {import('../../schema.js').Schema<delta.DeltaAny>}
187
+ */
188
+ this.childSchema = childSchemaOf($d)
189
+ }
190
+
191
+ get name () { return 'lib0:children' }
192
+
193
+ /**
194
+ * @return {Transformer<IN, any>}
195
+ */
196
+ init () {
197
+ return new ChildrenTransformer(this.$in, this.$out, this.handler, this.childSchema)
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Descend one level into a node's child *nodes* and apply a sub-transformer to each, chosen by
203
+ * `handler(childNode, $child)`: return a {@link Template} to transform that child, or `null` to leave
204
+ * it untransformed. The handler receives the derived per-child schema `$child` so it can build
205
+ * schema-bound sub-templates. Attributes and text pass through. The handler is evaluated once, when
206
+ * the child is inserted (returning `null` opts the child out permanently). Returns a reusable
207
+ * {@link Children} template (a `project` hole, or `.init()` for a standalone transformer).
208
+ *
209
+ * Composes recursively - e.g. inline every anonymous node at every depth:
210
+ * ```js
211
+ * const inlineAll = $d => children($d, (_c, $c) => pipe($c, $c1 => inline($c1, [null]), inlineAll))
212
+ * ```
213
+ *
214
+ * @template {delta.DeltaConf} IN
215
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
216
+ * @param {(child: delta.DeltaAny, $child: import('../../schema.js').Schema<delta.DeltaAny>) => Template | null} handler
217
+ * @return {Children<IN>}
218
+ */
219
+ export const children = ($d, handler) => new Children($d, handler)