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,401 @@
1
+ import * as delta from '../delta.js'
2
+ import * as s from '../../schema.js'
3
+ import * as math from '../../math.js'
4
+ import * as error from '../../error.js'
5
+ import { Transformer, Template, createTransformResult } from './core.js'
6
+
7
+ /**
8
+ * The literal node-names a `$Delta`'s `$name` schema accepts, or `null` when the name is loose
9
+ * (`$any`/`$string`) — such a schema acts as a wildcard that matches any node name.
10
+ *
11
+ * @param {import('../../schema.js').Schema<any>} $name
12
+ * @return {Array<string>?}
13
+ */
14
+ const literalNames = $name => s.$$literal.check($name) ? /** @type {Array<string>} */ ($name.shape) : null
15
+
16
+ /**
17
+ * The expanded matcher for a children / attr-value schema.
18
+ *
19
+ * @typedef {{ byName: Map<string, delta.$Delta<any>>, wild: delta.$Delta<any>?, passAny: boolean, $scalar: import('../../schema.js').Schema<any> }} Allowed
20
+ */
21
+
22
+ /**
23
+ * Expand a children / attr-value schema into the matcher conform uses. `byName`/`wild` give the `$Delta`
24
+ * a node conforms to, **matched by NAME** (so a node carrying not-yet-stripped attrs still matches — a
25
+ * full `.check` would be the wrong test, it would reject content conform is meant to strip). `passAny` is
26
+ * set when the schema accepts ANY content (a `$deltaAny` / `$any` member), so such content passes through
27
+ * verbatim with no nested transformer. `$scalar` checks a non-delta (JSON) value.
28
+ *
29
+ * @param {import('../../schema.js').Schema<any>} $s
30
+ * @return {Allowed}
31
+ */
32
+ const allowedOf = $s => {
33
+ /** @type {Map<string, delta.$Delta<any>>} */
34
+ const byName = new Map()
35
+ /** @type {delta.$Delta<any>?} */
36
+ let wild = null
37
+ let passAny = false
38
+ for (const m of (s.$$union.check($s) ? $s.shape : [$s])) {
39
+ if (m === delta.$deltaAny || s.$$any.check(m)) {
40
+ passAny = true
41
+ } else if (delta.$$delta.check(m)) {
42
+ const $m = /** @type {delta.$Delta<any>} */ (m)
43
+ const names = literalNames($m.shape.$name)
44
+ if (names === null) wild ??= $m
45
+ else for (const n of names) byName.set(n, $m)
46
+ }
47
+ }
48
+ return { byName, wild, passAny, $scalar: $s }
49
+ }
50
+
51
+ /** @type {Allowed} an empty matcher (allows nothing) — the placeholder for a pass-through config */
52
+ const NONE = { byName: new Map(), wild: null, passAny: false, $scalar: s.$never }
53
+ /** @type {Map<string|number, Allowed>} a shared empty attr matcher map (never mutated) */
54
+ const NO_ATTRS = new Map()
55
+
56
+ /**
57
+ * The read-only, schema-derived configuration of a conform transformer. Computed once per schema (see
58
+ * {@link configOf}) and shared by every transformer instance built from that schema — many instances are
59
+ * created for the children of one schema, so this avoids re-expanding the schema each time.
60
+ *
61
+ * @typedef {{ passthrough: boolean, hasText: boolean, childAllowed: Allowed, attrsLoose: boolean, attrAllowed: Map<string|number, Allowed> }} Config
62
+ */
63
+
64
+ /** @type {WeakMap<import('../../schema.js').Schema<any>, Config>} */
65
+ const configCache = new WeakMap()
66
+
67
+ /**
68
+ * The cached {@link Config} for `$schema`, computing (and memoizing) it on first use. `$deltaAny` yields
69
+ * a pass-through config (the conform is the identity).
70
+ *
71
+ * @param {import('../../schema.js').Schema<any>} $schema
72
+ * @return {Config}
73
+ */
74
+ const configOf = $schema => {
75
+ let c = configCache.get($schema)
76
+ if (c === undefined) {
77
+ if ($schema === delta.$deltaAny) {
78
+ c = { passthrough: true, hasText: false, childAllowed: NONE, attrsLoose: false, attrAllowed: NO_ATTRS }
79
+ } else {
80
+ const shape = /** @type {any} */ ($schema).shape
81
+ const attrsLoose = !s.$$object.check(shape.$attrs)
82
+ /** @type {Map<string|number, Allowed>} */
83
+ const attrAllowed = new Map()
84
+ if (!attrsLoose) {
85
+ const attrShape = /** @type {any} */ (shape.$attrs).shape
86
+ for (const key in attrShape) attrAllowed.set(key, allowedOf(attrShape[key]))
87
+ }
88
+ c = { passthrough: false, hasText: shape.hasText, childAllowed: allowedOf(shape.$children), attrsLoose, attrAllowed }
89
+ }
90
+ configCache.set($schema, c)
91
+ }
92
+ return c
93
+ }
94
+
95
+ /**
96
+ * A fresh nested conform bound to a sub-schema (a kept child or delta-valued attribute), reusing the
97
+ * memoized {@link Config} for that schema.
98
+ *
99
+ * @param {delta.$Delta<any>} $schema
100
+ * @return {ConformTransformer}
101
+ */
102
+ const nest = $schema => new ConformTransformer(delta.$deltaAny, $schema, configOf($schema))
103
+
104
+ /**
105
+ * Decide what conform does with one piece of new content (a child node or an attribute value):
106
+ * - `null` — drop it (it matches nothing the schema allows),
107
+ * - `true` — pass it through verbatim (a JSON scalar, which can't be modified anyway, or a node matched
108
+ * by a `$deltaAny`/`$any` schema, whose every future edit is valid),
109
+ * - a `ConformTransformer` — keep it but recursively conform it (a delta node matched to a *narrower*
110
+ * `$Delta`, whose future edits must still be stripped).
111
+ *
112
+ * @param {any} node
113
+ * @param {Allowed} allowed
114
+ * @return {ConformTransformer | true | null}
115
+ */
116
+ const needsTransform = (node, allowed) => {
117
+ if (delta.$deltaAny.check(node)) {
118
+ const $m = (node.name != null ? allowed.byName.get(node.name) : undefined) ?? allowed.wild
119
+ return $m != null ? nest($m) : (allowed.passAny ? true : null)
120
+ }
121
+ return allowed.$scalar.check(node) ? true : null
122
+ }
123
+
124
+ /**
125
+ * Whether `allowed` admits at least one delta value at this position — a `$deltaAny`/`$any` member, a
126
+ * wildcard `$Delta`, or any name-matched `$Delta`. A `modify` / `modifyAttr` op edits a delta child or
127
+ * delta-valued attribute, so it can only conform where this holds; against a scalar/text-only matcher it
128
+ * is necessarily invalid. Used by {@link ConformTransformer#applyB} to reject such modifies.
129
+ *
130
+ * @param {Allowed} allowed
131
+ * @return {boolean}
132
+ */
133
+ const admitsDelta = allowed => allowed.passAny || allowed.wild != null || allowed.byName.size > 0
134
+
135
+ /**
136
+ * Makes the projected (side-B) delta conform to `$schema`, **recursively**: drops attributes, attribute
137
+ * values, child nodes, and text the schema does not recognize, and descends into kept delta-valued
138
+ * attributes and kept child nodes with a nested `conform` (a fresh instance per kept child/attr, kept in
139
+ * step with the content — modelled on `children` + `inline`). A child is kept iff its node-name matches
140
+ * one of the schema's child `$Delta`s; a non-delta child iff it validates against `$children`; text iff
141
+ * the schema allows text. Everything else is dropped, so the output is guaranteed to satisfy `$schema`.
142
+ *
143
+ * Content that needs no per-position handling (text, scalars, and nodes matched by a `$deltaAny`/`$any`
144
+ * schema) is coalesced into pass-through runs, so a `$deltaAny` schema is a true zero-overhead identity
145
+ * and an incremental edit costs O(change), not O(document).
146
+ *
147
+ * `applyA` (A → B) does the full recursive filtering. `applyB` (B → A) is a lightweight reverse: it
148
+ * validates each B-side op against `$schema` — throwing on any op that would break conformance — and
149
+ * otherwise passes the change through to A verbatim (a conformant B-edit is already valid on A). Full
150
+ * drop-aware B→A position remapping is deferred (see {@link ConformTransformer#applyB}).
151
+ * Marks are best-effort — marks on kept children/attrs ride through the position map; marks anchored to
152
+ * dropped content are dropped.
153
+ *
154
+ * @template {delta.DeltaConf} SchemaConf
155
+ * @template {delta.DeltaConf} [IN=any]
156
+ * @extends {Template<IN, SchemaConf>}
157
+ */
158
+ export class Conform extends Template {
159
+ /**
160
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d input delta schema
161
+ * @param {import('../../schema.js').Schema<delta.Delta<SchemaConf>>} $schema the schema the output must match
162
+ */
163
+ constructor ($d, $schema) {
164
+ if (/** @type {any} */ ($schema) !== delta.$deltaAny) s.assert($schema, delta.$$delta)
165
+ super($d, /** @type {any} */ ($schema))
166
+ this.$schema = $schema
167
+ // expand the schema ONCE here (memoized); every transformer this template builds reuses it
168
+ this.config = configOf($schema)
169
+ }
170
+
171
+ get name () { return 'lib0:conform' }
172
+
173
+ /**
174
+ * @return {Transformer<IN, SchemaConf>}
175
+ */
176
+ init () {
177
+ return /** @type {any} */ (new ConformTransformer(this.$in, this.$out, this.config))
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Stateful, recursive transformer produced by {@link Conform}. See {@link Conform} for semantics.
183
+ *
184
+ * The A→B layout lives in {@link ConformTransformer#cmap} (the *child* map), a delta reused as a
185
+ * coalesced positional map (like `children`'s `childTs`): a `retain(n)` run = pass-through positions, an
186
+ * `insert([t])` = a child routed through nested conform `t`, a `delete(n)` run = dropped positions
187
+ * (width 0 on B). It is rebuilt only on a structural change, carrying the nested transformers by
188
+ * reference so their state survives; a change with only retains/modifies is routed in place.
189
+ *
190
+ * @extends {Transformer<any,any>}
191
+ */
192
+ export class ConformTransformer extends Transformer {
193
+ /**
194
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $in
195
+ * @param {import('../../schema.js').Schema<delta.Delta<any>>} $out
196
+ * @param {Config} config the schema-derived read-only config (computed once, see {@link configOf})
197
+ */
198
+ constructor ($in, $out, config) {
199
+ super($in, $out)
200
+ this.config = config
201
+ /** @type {delta.DeltaBuilderAny} the coalesced child layout map */
202
+ this.cmap = delta.create()
203
+ /** @type {Map<string|number, ConformTransformer>} nested conforms for delta-valued attributes */
204
+ this.transformAttrs = new Map()
205
+ }
206
+
207
+ /**
208
+ * Map an A-side change to a conformant B-side change. Unrecognized attributes/values/children/text are
209
+ * dropped; recognized delta children and delta-valued attributes are recursively conformed.
210
+ *
211
+ * @param {delta.DeltaBuilderAny} dA
212
+ * @return {import('./core.js').TransformResultAny}
213
+ */
214
+ applyA (dA) {
215
+ const cfg = this.config
216
+ if (cfg.passthrough) return createTransformResult(null, dA) // identity fast-path, no walk, no copy
217
+ const out = /** @type {any} */ (delta.create(/** @type {any} */ (dA.name)))
218
+ /** @type {Set<string|number>} */
219
+ const keptAttrKeys = new Set()
220
+ // --- attributes ---
221
+ for (const op of dA.attrs) {
222
+ const key = op.key
223
+ if (cfg.attrsLoose) {
224
+ // loose schema attrs ($any): keep every attribute verbatim (`out` is `any`, so the dynamic-key
225
+ // write is fine; this mirrors how `slice`/`cloneShallow` copy attr ops)
226
+ out.attrs[key] = op.clone()
227
+ keptAttrKeys.add(key)
228
+ continue
229
+ }
230
+ const allowed = cfg.attrAllowed.get(key)
231
+ if (allowed === undefined) continue // unknown attribute -> drop
232
+ if (delta.$setAttrOp.check(op)) {
233
+ const r = needsTransform(op.value, allowed)
234
+ if (r === null) continue
235
+ if (r === true) { out.setAttr(key, op.value, op.attribution); this.transformAttrs.delete(key) } else { this.transformAttrs.set(key, r); out.setAttr(key, r.applyA(op.value).b, op.attribution) }
236
+ keptAttrKeys.add(key)
237
+ } else if (delta.$modifyAttrOp.check(op)) {
238
+ let t = this.transformAttrs.get(key)
239
+ if (t === undefined) {
240
+ // no setAttr was seen first: a $deltaAny attr forwards verbatim; a narrowed delta attr builds
241
+ // its nested conform lazily; a scalar attr has nothing to modify (drop)
242
+ if (allowed.passAny) { out.modifyAttr(key, op.value); keptAttrKeys.add(key); continue }
243
+ const $m = allowed.wild ?? (allowed.byName.size > 0 ? /** @type {delta.$Delta<any>} */ (allowed.byName.values().next().value) : null)
244
+ if ($m != null) { t = nest($m); this.transformAttrs.set(key, t) }
245
+ }
246
+ if (t != null) { out.modifyAttr(key, t.applyA(op.value).b); keptAttrKeys.add(key) }
247
+ } else { // deleteAttr (the only remaining attr-op kind)
248
+ const dop = /** @type {delta.DeleteAttrOp<any>} */ (op)
249
+ out.deleteAttr(key, dop.attribution)
250
+ this.transformAttrs.delete(key)
251
+ keptAttrKeys.add(key)
252
+ }
253
+ }
254
+ // --- children: one cursor `(src, off)` walks `this.cmap`, editing it in place ---
255
+ // `cmap` is the coalesced A→B child layout: retain = pass-through, insert([t]) = a child routed
256
+ // through nested conform `t`, delete = dropped (B-width 0). retain/modify advance the cursor without
257
+ // touching the map; insert/text/delete edit it in place via delta's @internal child mutators (which
258
+ // keep `cmap.childCnt` in sync). Same-kind insertion grows the run in place (no split), so the map
259
+ // stays coalesced — typing into a pass-through run is O(1) and never fragments. The untouched tail
260
+ // past the cursor is simply left in place (no copy), so a structural edit costs O(change).
261
+ const cmap = this.cmap
262
+ let src = cmap.children.start
263
+ let off = 0
264
+ // move the cursor onto an op boundary so a fresh op can be inserted there
265
+ const splitCursor = () => { if (off > 0 && src != null) { src = delta._splitChildAt(cmap, src, off); off = 0 } }
266
+ // splice one run into the map at the cursor: grow the current op when it is the same kind (no split),
267
+ // else split + insert a fresh op + coalesce. `$kind`/`make` describe a uniform retain/delete run.
268
+ /**
269
+ * @param {import('../../schema.js').Schema<any>} $kind
270
+ * @param {number} n
271
+ * @param {() => delta.RetainOp | delta.DeleteOp<any>} make
272
+ */
273
+ const addUniform = ($kind, n, make) => {
274
+ if (src != null && $kind.check(src)) { delta._growRun(cmap, /** @type {delta.RetainOp | delta.DeleteOp<any>} */ (src), n); off += n } else { splitCursor(); delta._mergeChildWithPrev(cmap, delta._insertChild(cmap, src, make())) }
275
+ }
276
+ /** @param {number} n */
277
+ const addRetain = n => addUniform(delta.$retainOp, n, () => new delta.RetainOp(n, null, null)) // n pass-through positions
278
+ /** @param {number} n */
279
+ const addDrop = n => addUniform(delta.$deleteOp, n, () => new delta.DeleteOp(n)) // n dropped positions
280
+ /** @param {ConformTransformer} t */
281
+ const addTransformed = t => { // one child routed through nested conform `t`
282
+ if (src != null && delta.$insertOp.check(src)) { delta._spliceInsert(cmap, src, off, [t]); off += 1 } else { splitCursor(); delta._mergeChildWithPrev(cmap, delta._insertChild(cmap, src, new delta.InsertOp([t], null, null))) }
283
+ }
284
+ for (const op of dA.children) {
285
+ if (delta.$retainOp.check(op)) {
286
+ let rem = op.retain
287
+ while (rem > 0 && src != null) {
288
+ const take = math.min(src.length - off, rem)
289
+ if (!delta.$deleteOp.check(src)) out.retain(take, op.format, op.attribution) // pass & transform: width = take
290
+ off += take; rem -= take
291
+ if (off >= src.length) { src = src.next; off = 0 }
292
+ }
293
+ } else if (delta.$modifyOp.check(op)) {
294
+ if (src != null) {
295
+ if (delta.$insertOp.check(src)) out.modify(/** @type {ConformTransformer} */ (src.insert[off]).applyA(delta.clone(op.value)).b, op.format, op.attribution)
296
+ else if (delta.$retainOp.check(src)) out.modify(delta.clone(op.value), op.format, op.attribution) // pass-through child
297
+ // dropped child (delete in map): emit nothing
298
+ off += 1; if (off >= src.length) { src = src.next; off = 0 }
299
+ }
300
+ } else if (delta.$textOp.check(op)) {
301
+ if (cfg.hasText) { out.insert(op.insert, op.format, op.attribution); addRetain(op.insert.length) } else addDrop(op.insert.length)
302
+ } else if (delta.$insertOp.check(op)) {
303
+ for (const el of op.insert) {
304
+ const r = needsTransform(el, cfg.childAllowed)
305
+ if (r === null) addDrop(1) // dropped: no name match, no scalar match
306
+ else if (r === true) { out.insert([el], op.format, op.attribution); addRetain(1) } else { out.insert([r.applyA(el).b], op.format, op.attribution); addTransformed(r) }
307
+ }
308
+ } else { // delete: remove the deleted A-positions from the map
309
+ let rem = /** @type {delta.DeleteOp<any>} */ (op).delete
310
+ while (rem > 0 && src != null) {
311
+ const take = math.min(src.length - off, rem)
312
+ if (!delta.$deleteOp.check(src)) out.delete(take) // pass & transform had B-width; dropped had 0
313
+ if (off === 0 && take === src.length) { const next = src.next; delta._removeChild(cmap, src); src = next } else { delta._shrinkChild(cmap, src, off, take); if (off >= src.length) { src = src.next; off = 0 } }
314
+ rem -= take
315
+ }
316
+ // the deletion may have joined two same-kind runs: merge, keeping the cursor on the survivor
317
+ if (src != null) {
318
+ const prev = src.prev
319
+ const prevLen = prev != null ? prev.length : 0
320
+ if (delta._mergeChildWithPrev(cmap, src)) { src = prev; off += prevLen }
321
+ }
322
+ }
323
+ }
324
+ // --- marks (best-effort): map a content offset through the coalesced layout (O(#runs)) ---
325
+ if (dA.marks !== null || dA.deleteMarks !== null) {
326
+ delta.mergeRootMarks(out, dA, k => {
327
+ if (typeof k !== 'number') return keptAttrKeys.has(k) ? k : null // attr mark rides iff kept
328
+ let a = 0; let b = 0
329
+ for (const cop of this.cmap.children) {
330
+ if (a >= k) break
331
+ const take = math.min(cop.length, k - a)
332
+ if (!delta.$deleteOp.check(cop)) b += take // pass & transform keep width; drop contributes 0
333
+ a += take
334
+ }
335
+ return b
336
+ })
337
+ }
338
+ out.done(false)
339
+ return createTransformResult(null, out)
340
+ }
341
+
342
+ /**
343
+ * Map a B-side change back to A. The conformed view B already satisfies `$schema`, so a change
344
+ * authored on it is also valid on A and passes straight through — the result is `{ a: dB, b: null }`
345
+ * (mirroring `applyA`'s `{ a: null, b: out }`). Before donating it, every op is validated against the
346
+ * schema and any op that would break conformance **throws**: text where the schema forbids text, an
347
+ * `insert` / `setAttr` whose content fails the schema, an unknown attribute key, or a `modify` /
348
+ * `modifyAttr` targeting a position the schema gives no delta.
349
+ *
350
+ * Two things are intentionally out of scope in v1: the nested change of a `modify` is not
351
+ * deep-validated (only that a delta is permissible there), and B→A coordinates are passed through
352
+ * verbatim — exact when nothing was dropped (B is structurally identical to A); a full drop-aware
353
+ * position remapping through `cmap` is deferred.
354
+ *
355
+ * @param {delta.DeltaBuilderAny} dB
356
+ * @return {import('./core.js').TransformResultAny}
357
+ */
358
+ applyB (dB) {
359
+ const cfg = this.config
360
+ if (cfg.passthrough) return createTransformResult(dB, null) // identity: every op conforms
361
+ if (!cfg.attrsLoose) { // loose attrs ($any) accept any key/value — nothing to validate
362
+ for (const op of dB.attrs) {
363
+ const allowed = cfg.attrAllowed.get(op.key)
364
+ if (allowed === undefined) throw error.create('[lib0/delta] conform: unknown attribute "' + op.key + '"')
365
+ if (delta.$setAttrOp.check(op)) {
366
+ if (!allowed.$scalar.check(op.value)) throw error.create('[lib0/delta] conform: attribute "' + op.key + '" value does not conform to the schema')
367
+ } else if (delta.$modifyAttrOp.check(op)) {
368
+ if (!admitsDelta(allowed)) throw error.create('[lib0/delta] conform: attribute "' + op.key + '" is a scalar and cannot be modified')
369
+ }
370
+ // deleteAttr: removing an attribute always conforms
371
+ }
372
+ }
373
+ for (const op of dB.children) {
374
+ if (delta.$textOp.check(op)) {
375
+ if (!cfg.hasText) throw error.create('[lib0/delta] conform: text is not allowed by the schema')
376
+ } else if (delta.$insertOp.check(op)) {
377
+ for (const el of op.insert) if (!cfg.childAllowed.$scalar.check(el)) throw error.create('[lib0/delta] conform: inserted content does not conform to the schema')
378
+ } else if (delta.$modifyOp.check(op)) {
379
+ if (!admitsDelta(cfg.childAllowed)) throw error.create('[lib0/delta] conform: the schema has no delta child to modify')
380
+ }
381
+ // retain / delete: structural, no content -> always conform
382
+ }
383
+ return createTransformResult(dB, null) // every op conforms -> donate the change to A verbatim
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Make the projected (side-B) delta conform to `$schema`, recursively dropping every attribute, value,
389
+ * child node, and text run the schema does not recognize and descending into kept delta children /
390
+ * delta-valued attributes. Content the schema accepts passes through with near-zero overhead;
391
+ * `conform($d, delta.$deltaAny)` is the identity. Returns a reusable {@link Conform} template (a
392
+ * `project` hole, or `.init()` for a standalone transformer). `applyB` (B → A) validates a B-side
393
+ * change against `$schema` and passes it through, throwing on any non-conformant op.
394
+ *
395
+ * @template {delta.DeltaConf} IN
396
+ * @template {delta.DeltaConf} SchemaConf
397
+ * @param {import('../../schema.js').Schema<delta.Delta<IN>>} $d
398
+ * @param {import('../../schema.js').Schema<delta.Delta<SchemaConf>>} $schema
399
+ * @return {Conform<SchemaConf, IN>}
400
+ */
401
+ export const conform = ($d, $schema) => new Conform($d, $schema)