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,323 @@
1
+ import * as s from '../../schema.js'
2
+ import * as error from '../../error.js'
3
+ import * as delta from '../delta.js'
4
+ import * as fingerprintTrait from '../../trait/fingerprint.js'
5
+
6
+ /**
7
+ * @template {import('../delta.js').DeltaConf} [A={}]
8
+ * @template {import('../delta.js').DeltaConf} [B={}]
9
+ */
10
+ class TransformResult {
11
+ /**
12
+ * @param {import('../delta.js').DeltaBuilder<A>?} a
13
+ * @param {import('../delta.js').DeltaBuilder<B>?} b
14
+ */
15
+ constructor (a, b) {
16
+ /**
17
+ * @type {import('../delta.js').DeltaBuilder<A>?}
18
+ */
19
+ this.a = a
20
+ /**
21
+ * @type {import('../delta.js').DeltaBuilder<B>?}
22
+ */
23
+ this.b = b
24
+ }
25
+
26
+ isEmpty () {
27
+ return this.a == null && this.b == null
28
+ }
29
+
30
+ clear () {
31
+ this.a = null
32
+ this.b = null
33
+ }
34
+
35
+ /**
36
+ * @param {import('../delta.js').DeltaBuilder<A>?} a
37
+ */
38
+ applyA (a) {
39
+ if (a !== null) {
40
+ if (this.a == null) {
41
+ this.a = a // adopt: `a` is donated to this result (callers must not reuse it)
42
+ } else {
43
+ this.a.apply(a, { move: true }) // ...so the merge may consume `a` rather than freeze-copy it
44
+ }
45
+ }
46
+ return this
47
+ }
48
+
49
+ /**
50
+ * @param {import('../delta.js').DeltaBuilder<B>?} b
51
+ */
52
+ applyB (b) {
53
+ if (b !== null) {
54
+ if (this.b == null) {
55
+ this.b = b // adopt: `b` is donated to this result (callers must not reuse it)
56
+ } else {
57
+ this.b.apply(b, { move: true }) // ...so the merge may consume `b` rather than freeze-copy it
58
+ }
59
+ }
60
+ return this
61
+ }
62
+
63
+ reverse () {
64
+ return new TransformResult(this.b, this.a)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * @typedef {TransformResult<any,any>} TransformResultAny
70
+ */
71
+
72
+ /**
73
+ * @template {import('../delta.js').DeltaConf} A
74
+ * @template {import('../delta.js').DeltaConf} B
75
+ * @param {s.Schema<import('../delta.js').Delta<A>>} $a
76
+ * @param {s.Schema<import('../delta.js').Delta<B>>} $b
77
+ * @return {s.Schema<TransformResult<A,B>>}
78
+ */
79
+ export const $tresult = ($a, $b) => /** @type {any} */ (s.$instanceOf(TransformResult, tr => (tr.a === null || $a.check(tr.a)) && (tr.b === null || $b.check(tr.b))))
80
+
81
+ /**
82
+ * Build a {@link TransformResult}. Exposed so transformers in `./` can construct their results.
83
+ *
84
+ * @template {import('../delta.js').DeltaBuilderAny} [DeltaA=import('../delta.js').DeltaBuilderAny]
85
+ * @template {import('../delta.js').DeltaBuilderAny} [DeltaB=import('../delta.js').DeltaBuilderAny]
86
+ * @param {DeltaA?} a
87
+ * @param {DeltaB?} b
88
+ */
89
+ export const createTransformResult = (a, b) => new TransformResult(a, b)
90
+
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
+ *
107
+ * @template {import('../delta.js').DeltaConf} A
108
+ * @template {import('../delta.js').DeltaConf} B
109
+ */
110
+ export class Transformer {
111
+ /**
112
+ * @param {s.Schema<import('../delta.js').Delta<A>>} $in input delta schema
113
+ * @param {s.Schema<import('../delta.js').Delta<B>>} $out output delta schema (computed by the factory)
114
+ */
115
+ constructor ($in, $out) {
116
+ /**
117
+ * @type {s.Schema<import('../delta.js').Delta<A>>}
118
+ */
119
+ this.$in = $in
120
+ /**
121
+ * @type {s.Schema<import('../delta.js').Delta<B>>}
122
+ */
123
+ this.$out = $out
124
+ }
125
+
126
+ /**
127
+ * @param {TransformResult<A, B>} tin
128
+ * @return {TransformResult<A,B>}
129
+ */
130
+ apply (tin) {
131
+ const ta = tin.a
132
+ const tb = tin.b
133
+ const ares = ta != null ? this.applyA(ta) : createTransformResult(null, null)
134
+ // transform tb if necessary
135
+ if (tb != null) {
136
+ if (ares.b != null) {
137
+ tb.rebase(ares.b, false)
138
+ }
139
+ const bres = this.applyB(tb) // a fresh, single-use result -> its sides may be consumed (moved)
140
+ if (ares.a) {
141
+ ares.a.apply(bres.a, { move: true })
142
+ } else {
143
+ ares.a = bres.a
144
+ }
145
+ if (ares.b) {
146
+ ares.b.apply(bres.b, { move: true })
147
+ } else {
148
+ ares.b = bres.b
149
+ }
150
+ }
151
+ return ares
152
+ }
153
+
154
+ /**
155
+ * @param {import('../delta.js').DeltaBuilder<A>} t
156
+ * @return {TransformResult<A,B>}
157
+ */
158
+ applyA (t) {
159
+ return this.apply(createTransformResult(t, null))
160
+ }
161
+
162
+ /**
163
+ * @param {import('../delta.js').DeltaBuilder<B>} t
164
+ * @return {TransformResult<A,B>}
165
+ */
166
+ applyB (t) {
167
+ return this.apply(createTransformResult(null, t))
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Recognize a {@link Transformer} by its `$type` tag. Every value is either a `Transformer` or a
173
+ * {@link Template}, never both, so the two roles can each own a single prototype `$type`.
174
+ *
175
+ * @type {s.Schema<Transformer<any,any>>}
176
+ */
177
+ export const $transformer = /** @type {s.Schema<Transformer<any,any>>} */ (Transformer.prototype.$type = s.$type('d:transformer', Transformer))
178
+
179
+ /**
180
+ * Like {@link $transformer}, but carries the side-A / side-B delta types for typechecking; the two
181
+ * schema params do not constrain the runtime check.
182
+ *
183
+ * @template {import('../delta.js').DeltaConf} A
184
+ * @template {import('../delta.js').DeltaConf} B
185
+ * @param {s.Schema<import('../delta.js').Delta<A>>|A} _a
186
+ * @param {s.Schema<import('../delta.js').Delta<B>>|A} _b
187
+ * @return {s.Schema<Transformer<A,B>>}
188
+ */
189
+ /* @__NO_SIDE_EFFECTS__ */
190
+ export const transformerWith = (_a, _b) => /** @type {s.Schema<Transformer<A,B>>} */ ($transformer)
191
+
192
+ /**
193
+ * A function from an input delta schema to a {@link Template} — the schema-deferred form of a
194
+ * transformer factory. Used for `pipe` stages, recursive composition, and `bind`.
195
+ *
196
+ * @template {import('../delta.js').DeltaConf} In
197
+ * @template {import('../delta.js').DeltaConf} Out
198
+ * @typedef {($in: s.Schema<import('../delta.js').Delta<In>>) => Template<In, Out>} TemplateFactory
199
+ */
200
+
201
+ /**
202
+ * Force a factory's computed output conf to display fully-resolved (flatten `Omit<…> & {…}` / bare
203
+ * alias artifacts) while staying a valid `DeltaConf` for use as a `Transformer`/`Delta` type
204
+ * argument — conf-aware and depth-limited (nested `Delta<…>` stay intact). Implemented as an
205
+ * overwrite-with-nothing, reusing {@link import('../delta.js').DeltaConfOverwrite}'s
206
+ * prettify+coerce recipe. Apply to every factory's `OUT` typedef whose result is not already
207
+ * produced by `DeltaConfOverwrite`.
208
+ *
209
+ * @template {import('../delta.js').DeltaConf} Conf
210
+ * @typedef {import('../delta.js').DeltaConfOverwrite<Conf, {}>} ResolveOut
211
+ */
212
+
213
+ /**
214
+ * The reusable, schema-bound form of a transformer: built by a factory (`attr($d, …)`), it holds the
215
+ * input/output delta schemas (`$in`/`$out`) and materializes a fresh stateful {@link Transformer} via
216
+ * the parameterless `init()`. Concrete templates `extend Template`.
217
+ *
218
+ * Templates are {@link fingerprintTrait.Fingerprintable}: this is what lets them be embedded as values
219
+ * (holes) in a `delta.create(...)` projection spec without collapsing the spec's type.
220
+ *
221
+ * @template {import('../delta.js').DeltaConf} [IN=any]
222
+ * @template {import('../delta.js').DeltaConf} [OUT=any]
223
+ */
224
+ export class Template {
225
+ /**
226
+ * @param {s.Schema<import('../delta.js').Delta<IN>>} $in input delta schema
227
+ * @param {s.Schema<import('../delta.js').Delta<OUT>>} $out output delta schema (computed by the factory)
228
+ */
229
+ constructor ($in, $out) {
230
+ /**
231
+ * @type {s.Schema<import('../delta.js').Delta<IN>>}
232
+ */
233
+ this.$in = $in
234
+ /**
235
+ * @type {s.Schema<import('../delta.js').Delta<OUT>>}
236
+ */
237
+ this.$out = $out
238
+ }
239
+
240
+ /**
241
+ * Stable, mangle-safe identifier folded into this template's fingerprint. Subclasses with
242
+ * configuration override it so that e.g. `attr('a')` and `attr('b')` fingerprint differently
243
+ * (do NOT use `constructor.name` — class names do not survive mangling).
244
+ *
245
+ * @return {string}
246
+ */
247
+ get name () { return 'lib0:template' }
248
+
249
+ /**
250
+ * @return {string}
251
+ */
252
+ [fingerprintTrait.FingerprintTraitSymbol] () { return this.name }
253
+
254
+ /**
255
+ * Instantiate a fresh stateful {@link Transformer} bound to this template's schema.
256
+ *
257
+ * @return {Transformer<IN, OUT>}
258
+ */
259
+ /* c8 ignore next 3 */
260
+ init () {
261
+ return error.methodUnimplemented()
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Bind an input schema into the `$d => Template` lambda pattern (the shape used by {@link pipe}
267
+ * stages, {@link import('./children.js').children} handlers, and `bind`) at the top level: calls
268
+ * `fn` with `$d` and returns its {@link Template}. Lets you write `transform($d, $d => pipe($d, …))`
269
+ * — keeping every template construction in the same lambda style — without a separate `const $d`
270
+ * statement. The lambda's `$d` parameter is typed as the input delta schema, and the concrete
271
+ * template subtype `fn` returns is preserved in the result.
272
+ *
273
+ * @template {import('../delta.js').DeltaConf} IN
274
+ * @template {Template<IN, any>} T
275
+ * @param {s.Schema<import('../delta.js').Delta<IN>>} $d
276
+ * @param {($d: s.Schema<import('../delta.js').Delta<IN>>) => T} fn
277
+ * @return {T}
278
+ */
279
+ export const transform = ($d, fn) => fn($d)
280
+
281
+ /**
282
+ * Rebuild `$in`'s delta schema with a replaced attrs-shape map (keeping name/children/text). Used by
283
+ * the attr-renaming/filtering factories to compute a concrete output schema (`$out`). Falls back to
284
+ * `$deltaAny` when `$in` is loose (not a concrete `$delta(...)`).
285
+ *
286
+ * @param {s.Schema<import('../delta.js').DeltaAny>} $in
287
+ * @param {{[k:string|number]: s.Schema<any>}} newAttrsShape
288
+ * @return {s.Schema<import('../delta.js').DeltaAny>}
289
+ */
290
+ export const withAttrs = ($in, newAttrsShape) => delta.$$delta.check($in)
291
+ ? new delta.$Delta($in.shape.$name, s.$object(newAttrsShape), $in.shape.$children, $in.shape.hasText, false, $in.shape.$formats)
292
+ : delta.$deltaAny
293
+
294
+ /**
295
+ * Rebuild `$in`'s delta schema with a replaced node `name` (keeping attrs/children/text). Falls back
296
+ * to `$deltaAny` when `$in` is loose.
297
+ *
298
+ * @param {s.Schema<import('../delta.js').DeltaAny>} $in
299
+ * @param {string} name
300
+ * @return {s.Schema<import('../delta.js').DeltaAny>}
301
+ */
302
+ export const withName = ($in, name) => delta.$$delta.check($in)
303
+ ? new delta.$Delta(s.$(name), $in.shape.$attrs, $in.shape.$children, $in.shape.hasText, false, $in.shape.$formats)
304
+ : delta.$deltaAny
305
+
306
+ /**
307
+ * The `{ attrKey: Schema }` map of `$in`'s attrs, or `null` when `$in` is loose / has no object attrs
308
+ * schema. Lets attr-renaming/filtering factories compute a concrete `$out`.
309
+ *
310
+ * @param {s.Schema<import('../delta.js').DeltaAny>} $in
311
+ * @return {{[k:string|number]: s.Schema<any>}?}
312
+ */
313
+ export const attrsShapeOf = ($in) => delta.$$delta.check($in) && s.$$object.check($in.shape.$attrs)
314
+ ? /** @type {{[k:string|number]: s.Schema<any>}} */ ($in.shape.$attrs.shape)
315
+ : null
316
+
317
+ /**
318
+ * Recognize a {@link Template} by its `$type` tag. Used to detect transformer "holes" embedded in a
319
+ * `project` spec.
320
+ *
321
+ * @type {s.Schema<Template>}
322
+ */
323
+ export const $template = /** @type {s.Schema<Template>} */ (Template.prototype.$type = s.$type('d:template', Template))
@@ -0,0 +1,282 @@
1
+ import * as delta from '../delta.js'
2
+ import * as s from '../../schema.js'
3
+ import * as object from '../../object.js'
4
+ import * as math from '../../math.js'
5
+ import { Transformer, Template, createTransformResult } from './core.js'
6
+
7
+ /**
8
+ * # `fullAttributions`
9
+ *
10
+ * A **stateful** transformer that normalizes the `attribution` dimension: whenever an op changes
11
+ * attribution, it re-emits the **complete accumulated** attribution for that position instead of just
12
+ * the incremental change. So a stream like `insert('a', undefined, { insert: [] })` then
13
+ * `retain(1, undefined, { insertAt: 4 })` is forwarded as `insert('a', …, { insert: [] })` then
14
+ * `retain(1, undefined, { insert: [], insertAt: 4 })`. Content, formats, structure and marks pass
15
+ * through untouched. It is the natural upstream of {@link import('./attribution-to-format.js').attributionToFormat},
16
+ * lifting that transformer's "assume the full attribution is present" requirement.
17
+ *
18
+ * State is a private **content-free attribution overlay** (`this.overlay`): a delta mirroring the
19
+ * document shape but carrying no content — text/scalar runs are `retain(len, _, attr)`, child nodes are
20
+ * `modify(childOverlay, _, nodeAttr)`, node attributes are per-key attr ops — each holding that
21
+ * position's accumulated attribution as an *instruction* (`{k:v}` set, `{k:null}` cleared).
22
+ *
23
+ * Per `applyA(d)`: a single read-only walk over `d` (in step with the overlay) builds one content-free
24
+ * delta `full` carrying the resolved full attribution at exactly `d`'s touched positions. `full` is then
25
+ * used twice — `d.apply(full)` enriches the input *in place* (that is the output: no content is copied,
26
+ * `apply` only splits content-free retains), and `overlay.rebase(d)` + `overlay.apply(full)` update the
27
+ * state, both in place and bounded by the change. `applyB` is passthrough + overlay realign (the view
28
+ * never attributes).
29
+ *
30
+ * Object ownership: `applyA` mutates the input delta's **structure** in place (legitimate — the framework
31
+ * hands a transformer a non-done change builder to consume, cf. `children.js`) and returns it as the
32
+ * output. It never mutates a `format`/`attribution` **object** in place. We do **not** own those objects —
33
+ * they may belong to done (shared) data — so they are read-only here, and every attribution we emit is a
34
+ * freshly allocated object (`mergeLeaves`/`applyFull`, mirroring delta.js's `mergeAttr`). The overlay and
35
+ * the output only ever receive fresh top-level objects (`apply`/`rebase` merge via `mergeAttr`; `clone`
36
+ * shallow-copies via `_cloneAttrs`), so no shared object is updated through a reference. Nested `format`
37
+ * maps and leaf values are shared by reference exactly as delta.js shares them — safe because nothing is
38
+ * written in place.
39
+ *
40
+ * Per-change cost is O(change); the overlay is O(document attribution structure). See {@link fullAttributions}.
41
+ *
42
+ * @module delta/transformer/full-attributions
43
+ */
44
+
45
+ /**
46
+ * Instruction-form shallow merge of `update` into `base`: set every present key, **keep** a `{k:null}`
47
+ * removal verbatim (so it still clears downstream), skip `undefined`. Used for attribution leaves and for
48
+ * the nested `format` map. Mirrors `mergeShallow` with `resolve = false` (delta.js). Allocates a fresh
49
+ * object; never mutates `base`/`update` (they may be done, shared data — read-only).
50
+ *
51
+ * @param {{[k:string]:any}|null|undefined} base
52
+ * @param {{[k:string]:any}} update
53
+ * @return {{[k:string]:any}}
54
+ */
55
+ const mergeLeaves = (base, update) => {
56
+ const r = s.$objectAny.check(base) ? { ...base } : {}
57
+ for (const k in update) if (update[k] !== undefined) r[k] = update[k] // set, or keep `null` (instruction)
58
+ return r
59
+ }
60
+
61
+ /**
62
+ * The full attribution at a position after applying a tri-state attribution `update` onto the
63
+ * accumulated `base`, in instruction form. `undefined` ⇒ unchanged, `null` ⇒ clear all; otherwise a
64
+ * shallow tri-state merge whose single nested `format` key merges one level. Mirrors `mergeAttr` with
65
+ * `resolve = false` (delta.js) — kept in instruction form so a cleared key survives as `{k:null}` and is
66
+ * re-emitted (the "set present + clear removed" contract). Allocates a fresh object for the merge case;
67
+ * never mutates `base`/`update` (they may be done, shared data — read-only).
68
+ *
69
+ * @param {{[k:string]:any}|null|undefined} base
70
+ * @param {{[k:string]:any}|null|undefined} update
71
+ * @return {{[k:string]:any}|null|undefined}
72
+ */
73
+ const applyFull = (base, update) => {
74
+ if (update === undefined) return base
75
+ if (update === null) return null
76
+ const r = s.$objectAny.check(base) ? { ...base } : {}
77
+ for (const k in update) {
78
+ const v = update[k]
79
+ if (v === undefined) continue
80
+ if (k === 'format' && s.$objectAny.check(v)) {
81
+ const sub = mergeLeaves(r.format, v)
82
+ if (object.isEmpty(sub)) delete r.format
83
+ else r.format = sub
84
+ } else r[k] = v // leaf set, or `null` kept verbatim (instruction)
85
+ }
86
+ return r
87
+ }
88
+
89
+ /**
90
+ * Normalize a *data* op's attribution (insert/text/setAttr/deleteAttr) for storage in `full`: `null`
91
+ * means "none" (⇒ `undefined`, i.e. a gap), an object is already the complete attribution of new content.
92
+ *
93
+ * @param {{[k:string]:any}|null|undefined} a
94
+ * @return {{[k:string]:any}|undefined}
95
+ */
96
+ const dataAttr = a => (a == null ? undefined : a)
97
+
98
+ /**
99
+ * Build the content-free `full` delta for change `d` against the read-only `overlay` (the accumulated
100
+ * attribution, or `null` for freshly inserted content). Carries the resolved full attribution at exactly
101
+ * `d`'s touched positions; recurses into child nodes.
102
+ *
103
+ * @param {import('../delta.js').DeltaAny} d
104
+ * @param {import('../delta.js').DeltaAny|null} overlay
105
+ * @return {import('../delta.js').DeltaBuilderAny}
106
+ */
107
+ const buildFull = (d, overlay) => {
108
+ const full = /** @type {any} */ (delta.create())
109
+ let cur = overlay == null ? null : overlay.children.start
110
+ let off = 0
111
+ /**
112
+ * Advance the read cursor by `n` positions without reading (untouched retains, deletes).
113
+ * @param {number} n
114
+ */
115
+ const skip = n => {
116
+ let rem = n
117
+ while (rem > 0 && cur != null) {
118
+ const take = math.min(cur.length - off, rem)
119
+ off += take; rem -= take
120
+ if (off >= cur.length) { cur = cur.next; off = 0 }
121
+ }
122
+ }
123
+ /**
124
+ * Read the attribution run at the cursor (≤ `rem` positions), advancing. Beyond the overlay ⇒ no prior
125
+ * attribution.
126
+ * @param {number} rem
127
+ * @return {[number, {[k:string]:any}|null|undefined]}
128
+ */
129
+ const readRun = rem => {
130
+ if (cur == null) return [rem, undefined]
131
+ const take = math.min(cur.length - off, rem)
132
+ const attr = /** @type {any} */ (cur).attribution
133
+ off += take
134
+ if (off >= cur.length) { cur = cur.next; off = 0 }
135
+ return [take, attr]
136
+ }
137
+ /**
138
+ * The nested overlay + node-attribution at the cursor (a Modify run; else `null`/leaf attr), advancing
139
+ * one position.
140
+ * @return {{ nested: import('../delta.js').DeltaAny|null, attr: {[k:string]:any}|null|undefined }}
141
+ */
142
+ const readModify = () => {
143
+ const node = cur
144
+ const attr = node == null ? undefined : /** @type {any} */ (node).attribution
145
+ const nested = (node != null && delta.$modifyOp.check(node)) ? node.value : null
146
+ if (cur != null) { off += 1; if (off >= cur.length) { cur = cur.next; off = 0 } }
147
+ return { nested, attr }
148
+ }
149
+ for (const op of d.children) {
150
+ if (delta.$retainOp.check(op)) {
151
+ if (op.attribution === undefined) {
152
+ full.retain(op.retain); skip(op.retain) // untouched ⇒ gap
153
+ } else {
154
+ let rem = op.retain
155
+ while (rem > 0) {
156
+ const [take, runAttr] = readRun(rem)
157
+ full.retain(take, undefined, applyFull(runAttr, op.attribution))
158
+ rem -= take
159
+ }
160
+ }
161
+ } else if (delta.$textOp.check(op)) {
162
+ full.retain(op.insert.length, undefined, dataAttr(op.attribution)) // new content: attribution already full
163
+ } else if (delta.$insertOp.check(op)) {
164
+ for (const el of op.insert) {
165
+ if (delta.$deltaAny.check(el)) full.modify(buildFull(el, null), undefined, dataAttr(op.attribution))
166
+ else full.retain(1, undefined, dataAttr(op.attribution))
167
+ }
168
+ } else if (delta.$deleteOp.check(op)) {
169
+ skip(op.delete) // positions removed: nothing in `full`
170
+ } else { // $modifyOp
171
+ const { nested, attr } = readModify()
172
+ full.modify(buildFull(op.value, nested), undefined, applyFull(attr, op.attribution))
173
+ }
174
+ }
175
+ for (const aop of d.attrs) {
176
+ // `setAttr`/`deleteAttr` *replace* the attr op, so their attribution is already complete (pass it
177
+ // through unchanged). Only `modifyAttr` is an instruction that *merges* attribution — that is the one
178
+ // whose increment we expand against the overlay's accumulated attr attribution.
179
+ if (delta.$modifyAttrOp.check(aop)) {
180
+ const oa = overlay == null ? undefined : /** @type {any} */ (overlay).attrs[aop.key]
181
+ const childOverlay = (oa != null && delta.$modifyAttrOp.check(oa) && delta.$deltaAny.check(oa.value)) ? oa.value : null
182
+ full.modifyAttr(aop.key, delta.$deltaAny.check(aop.value) ? buildFull(aop.value, childOverlay) : aop.value, applyFull(oa == null ? undefined : oa.attribution, aop.attribution))
183
+ } else if (delta.$setAttrOp.check(aop)) {
184
+ full.setAttr(aop.key, aop.value, aop.attribution)
185
+ } else if (delta.$deleteAttrOp.check(aop)) {
186
+ full.deleteAttr(aop.key, aop.attribution)
187
+ }
188
+ }
189
+ full.done(false)
190
+ return full
191
+ }
192
+
193
+ /**
194
+ * Stateful transformer holding the accumulated attribution overlay.
195
+ *
196
+ * @extends {Transformer<any,any>}
197
+ */
198
+ export class FullAttributionsTransformer extends Transformer {
199
+ /**
200
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
201
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
202
+ */
203
+ constructor ($in, $out) {
204
+ super($in, $out)
205
+ /**
206
+ * Content-free overlay: the full accumulated attribution at every document position (instruction
207
+ * form). Mutated in place each change by `rebase` + `apply`.
208
+ *
209
+ * @type {delta.DeltaBuilderAny}
210
+ */
211
+ this.overlay = delta.create()
212
+ }
213
+
214
+ /**
215
+ * Forward: expand each attribution-bearing op to the complete accumulated attribution. Mutates and
216
+ * returns the input change.
217
+ *
218
+ * @param {delta.DeltaBuilderAny} d
219
+ * @return {import('./core.js').TransformResultAny}
220
+ */
221
+ applyA (d) {
222
+ const full = buildFull(d, this.overlay)
223
+ // STATE (in place): align/grow the overlay to post-change coords, then accumulate the increments.
224
+ this.overlay.rebase(d, true)
225
+ this.overlay.apply(full, { move: false })
226
+ // OUTPUT (in place): enrich the input — `apply` splits content-free retains; no content is copied.
227
+ d.apply(full, { move: true })
228
+ return createTransformResult(null, d)
229
+ }
230
+
231
+ /**
232
+ * Reverse: passthrough; only realign the overlay so positions track view inserts/deletes. The view
233
+ * never attributes, so there is nothing to expand.
234
+ *
235
+ * @param {delta.DeltaBuilderAny} d
236
+ * @return {import('./core.js').TransformResultAny}
237
+ */
238
+ applyB (d) {
239
+ this.overlay.rebase(d, true)
240
+ return createTransformResult(d, null)
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Template for {@link FullAttributionsTransformer}. The transform touches only the `attribution`
246
+ * dimension (metadata, not part of the delta schema), so the output schema equals the input schema.
247
+ *
248
+ * @template {delta.DeltaConf} [IN=any]
249
+ * @extends {Template<IN, IN>}
250
+ */
251
+ export class FullAttributions extends Template {
252
+ /**
253
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
254
+ */
255
+ constructor ($d) {
256
+ super($d, $d)
257
+ }
258
+
259
+ get name () { return 'lib0:fullAttributions' }
260
+
261
+ /**
262
+ * @return {Transformer<IN, IN>}
263
+ */
264
+ init () {
265
+ return new FullAttributionsTransformer(this.$in, this.$out)
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Normalize a delta's `attribution` dimension so every attribution change carries the **complete**
271
+ * accumulated attribution for that position (not just the increment) — see the
272
+ * {@link module:delta/transformer/full-attributions module doc}. Stateful: it keeps a content-free
273
+ * overlay of the accumulated attribution and updates it in place per change. `applyA` mutates and returns
274
+ * its input change. Returns a reusable {@link FullAttributions} template (`.init()` for a standalone
275
+ * transformer); typically piped before
276
+ * {@link import('./attribution-to-format.js').attributionToFormat}.
277
+ *
278
+ * @template {delta.DeltaConf} IN
279
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
280
+ * @return {FullAttributions<IN>}
281
+ */
282
+ export const fullAttributions = ($d) => new FullAttributions($d)
@@ -0,0 +1,13 @@
1
+ import { renameAttrs } from './rename-attrs.js'
2
+
3
+ /**
4
+ * The identity transformer factory: it maps every change verbatim in both directions, so both sides
5
+ * stay bit-for-bit equal (an attr-rename with no renames). The default template factory for
6
+ * {@link import('../rdt.js').bind}; `id($d).init()` builds the transformer.
7
+ *
8
+ * @template {import('../delta.js').DeltaConf} IN
9
+ * @param {import('../../schema.js').Schema<import('../delta.js').Delta<IN>>} $d
10
+ * @return {import('./rename-attrs.js').RenameAttrs<{}, IN>}
11
+ */
12
+ /* @__NO_SIDE_EFFECTS__ */
13
+ export const id = ($d) => renameAttrs($d, {})