@y/y 14.0.0-rc.2 → 14.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 (166) hide show
  1. package/README.md +36 -12
  2. package/dist/src/index.d.ts +24 -1
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/structs/AbstractStruct.d.ts +14 -17
  5. package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
  6. package/dist/src/structs/GC.d.ts +9 -14
  7. package/dist/src/structs/GC.d.ts.map +1 -1
  8. package/dist/src/structs/Item.d.ts +569 -31
  9. package/dist/src/structs/Item.d.ts.map +1 -1
  10. package/dist/src/structs/Skip.d.ts +9 -11
  11. package/dist/src/structs/Skip.d.ts.map +1 -1
  12. package/dist/src/utils/BlockSet.d.ts +8 -7
  13. package/dist/src/utils/BlockSet.d.ts.map +1 -1
  14. package/dist/src/utils/Doc.d.ts +4 -9
  15. package/dist/src/utils/Doc.d.ts.map +1 -1
  16. package/dist/src/utils/ID.d.ts +0 -1
  17. package/dist/src/utils/ID.d.ts.map +1 -1
  18. package/dist/src/utils/RelativePosition.d.ts +2 -5
  19. package/dist/src/utils/RelativePosition.d.ts.map +1 -1
  20. package/dist/src/utils/Renderer.d.ts +144 -0
  21. package/dist/src/utils/Renderer.d.ts.map +1 -0
  22. package/dist/src/utils/Snapshot.d.ts +7 -11
  23. package/dist/src/utils/Snapshot.d.ts.map +1 -1
  24. package/dist/src/utils/StructStore.d.ts +45 -23
  25. package/dist/src/utils/StructStore.d.ts.map +1 -1
  26. package/dist/src/utils/Transaction.d.ts +11 -21
  27. package/dist/src/utils/Transaction.d.ts.map +1 -1
  28. package/dist/src/utils/UndoManager.d.ts +13 -14
  29. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  30. package/dist/src/utils/UpdateDecoder.d.ts +1 -1
  31. package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
  32. package/dist/src/utils/UpdateEncoder.d.ts +0 -1
  33. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  34. package/dist/src/utils/YEvent.d.ts +22 -26
  35. package/dist/src/utils/YEvent.d.ts.map +1 -1
  36. package/dist/src/utils/content-helper.d.ts +2 -0
  37. package/dist/src/utils/content-helper.d.ts.map +1 -0
  38. package/dist/src/utils/delta-helpers.d.ts +2 -3
  39. package/dist/src/utils/delta-helpers.d.ts.map +1 -1
  40. package/dist/src/utils/encoding-helpers.d.ts +6 -0
  41. package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
  42. package/dist/src/utils/encoding.d.ts +16 -18
  43. package/dist/src/utils/encoding.d.ts.map +1 -1
  44. package/dist/src/utils/ids.d.ts +331 -0
  45. package/dist/src/utils/ids.d.ts.map +1 -0
  46. package/dist/src/utils/isParentOf.d.ts +1 -2
  47. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  48. package/dist/src/utils/logging.d.ts +0 -1
  49. package/dist/src/utils/logging.d.ts.map +1 -1
  50. package/dist/src/utils/meta.d.ts +15 -64
  51. package/dist/src/utils/meta.d.ts.map +1 -1
  52. package/dist/src/utils/renderer-helpers.d.ts +99 -0
  53. package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
  54. package/dist/src/utils/schemas.d.ts +3 -0
  55. package/dist/src/utils/schemas.d.ts.map +1 -0
  56. package/dist/src/utils/transaction-helpers.d.ts +18 -0
  57. package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
  58. package/dist/src/utils/updates.d.ts +19 -25
  59. package/dist/src/utils/updates.d.ts.map +1 -1
  60. package/dist/src/ytype.d.ts +144 -41
  61. package/dist/src/ytype.d.ts.map +1 -1
  62. package/global.d.ts +53 -0
  63. package/package.json +12 -16
  64. package/src/index.js +32 -124
  65. package/src/structs/AbstractStruct.js +21 -16
  66. package/src/structs/GC.js +15 -20
  67. package/src/structs/Item.js +992 -318
  68. package/src/structs/Skip.js +16 -19
  69. package/src/utils/BlockSet.js +20 -20
  70. package/src/utils/Doc.js +18 -29
  71. package/src/utils/ID.js +0 -2
  72. package/src/utils/RelativePosition.js +15 -25
  73. package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
  74. package/src/utils/Snapshot.js +15 -37
  75. package/src/utils/StructStore.js +89 -227
  76. package/src/utils/Transaction.js +89 -321
  77. package/src/utils/UndoManager.js +157 -19
  78. package/src/utils/UpdateDecoder.js +2 -3
  79. package/src/utils/UpdateEncoder.js +0 -4
  80. package/src/utils/YEvent.js +20 -26
  81. package/src/utils/content-helper.js +0 -0
  82. package/src/utils/delta-helpers.js +21 -42
  83. package/src/utils/encoding-helpers.js +110 -0
  84. package/src/utils/encoding.js +197 -122
  85. package/src/utils/ids.js +1527 -0
  86. package/src/utils/isParentOf.js +2 -4
  87. package/src/utils/logging.js +0 -4
  88. package/src/utils/meta.js +57 -46
  89. package/src/utils/renderer-helpers.js +110 -0
  90. package/src/utils/schemas.js +3 -0
  91. package/src/utils/transaction-helpers.js +413 -0
  92. package/src/utils/updates.js +24 -146
  93. package/src/ytype.js +626 -255
  94. package/tests/testHelper.js +10 -12
  95. package/dist/src/internals.d.ts +0 -36
  96. package/dist/src/internals.d.ts.map +0 -1
  97. package/dist/src/structs/ContentAny.d.ts +0 -67
  98. package/dist/src/structs/ContentAny.d.ts.map +0 -1
  99. package/dist/src/structs/ContentBinary.d.ts +0 -64
  100. package/dist/src/structs/ContentBinary.d.ts.map +0 -1
  101. package/dist/src/structs/ContentDeleted.d.ts +0 -64
  102. package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
  103. package/dist/src/structs/ContentDoc.d.ts +0 -72
  104. package/dist/src/structs/ContentDoc.d.ts.map +0 -1
  105. package/dist/src/structs/ContentEmbed.d.ts +0 -67
  106. package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
  107. package/dist/src/structs/ContentFormat.d.ts +0 -69
  108. package/dist/src/structs/ContentFormat.d.ts.map +0 -1
  109. package/dist/src/structs/ContentJSON.d.ts +0 -70
  110. package/dist/src/structs/ContentJSON.d.ts.map +0 -1
  111. package/dist/src/structs/ContentString.d.ts +0 -70
  112. package/dist/src/structs/ContentString.d.ts.map +0 -1
  113. package/dist/src/structs/ContentType.d.ts +0 -77
  114. package/dist/src/structs/ContentType.d.ts.map +0 -1
  115. package/dist/src/utils/AttributionManager.d.ts +0 -238
  116. package/dist/src/utils/AttributionManager.d.ts.map +0 -1
  117. package/dist/src/utils/IdMap.d.ts +0 -164
  118. package/dist/src/utils/IdMap.d.ts.map +0 -1
  119. package/dist/src/utils/IdSet.d.ts +0 -163
  120. package/dist/src/utils/IdSet.d.ts.map +0 -1
  121. package/dist/tests/IdMap.tests.d.ts +0 -9
  122. package/dist/tests/IdMap.tests.d.ts.map +0 -1
  123. package/dist/tests/IdSet.tests.d.ts +0 -9
  124. package/dist/tests/IdSet.tests.d.ts.map +0 -1
  125. package/dist/tests/attribution.tests.d.ts +0 -9
  126. package/dist/tests/attribution.tests.d.ts.map +0 -1
  127. package/dist/tests/compatibility.tests.d.ts +0 -5
  128. package/dist/tests/compatibility.tests.d.ts.map +0 -1
  129. package/dist/tests/delta.tests.d.ts +0 -7
  130. package/dist/tests/delta.tests.d.ts.map +0 -1
  131. package/dist/tests/doc.tests.d.ts +0 -13
  132. package/dist/tests/doc.tests.d.ts.map +0 -1
  133. package/dist/tests/encoding.tests.d.ts +0 -5
  134. package/dist/tests/encoding.tests.d.ts.map +0 -1
  135. package/dist/tests/index.d.ts +0 -2
  136. package/dist/tests/index.d.ts.map +0 -1
  137. package/dist/tests/relativePositions.tests.d.ts +0 -11
  138. package/dist/tests/relativePositions.tests.d.ts.map +0 -1
  139. package/dist/tests/snapshot.tests.d.ts +0 -14
  140. package/dist/tests/snapshot.tests.d.ts.map +0 -1
  141. package/dist/tests/testHelper.d.ts +0 -171
  142. package/dist/tests/testHelper.d.ts.map +0 -1
  143. package/dist/tests/undo-redo.tests.d.ts +0 -27
  144. package/dist/tests/undo-redo.tests.d.ts.map +0 -1
  145. package/dist/tests/updates.tests.d.ts +0 -26
  146. package/dist/tests/updates.tests.d.ts.map +0 -1
  147. package/dist/tests/y-array.tests.d.ts +0 -43
  148. package/dist/tests/y-array.tests.d.ts.map +0 -1
  149. package/dist/tests/y-map.tests.d.ts +0 -42
  150. package/dist/tests/y-map.tests.d.ts.map +0 -1
  151. package/dist/tests/y-text.tests.d.ts +0 -49
  152. package/dist/tests/y-text.tests.d.ts.map +0 -1
  153. package/dist/tests/y-xml.tests.d.ts +0 -14
  154. package/dist/tests/y-xml.tests.d.ts.map +0 -1
  155. package/src/internals.js +0 -35
  156. package/src/structs/ContentAny.js +0 -115
  157. package/src/structs/ContentBinary.js +0 -93
  158. package/src/structs/ContentDeleted.js +0 -101
  159. package/src/structs/ContentDoc.js +0 -141
  160. package/src/structs/ContentEmbed.js +0 -98
  161. package/src/structs/ContentFormat.js +0 -105
  162. package/src/structs/ContentJSON.js +0 -119
  163. package/src/structs/ContentString.js +0 -113
  164. package/src/structs/ContentType.js +0 -152
  165. package/src/utils/IdMap.js +0 -673
  166. package/src/utils/IdSet.js +0 -825
@@ -0,0 +1,1527 @@
1
+ import * as math from 'lib0/math'
2
+ import * as traits from 'lib0/traits'
3
+ import * as encoding from 'lib0/encoding'
4
+ import * as decoding from 'lib0/decoding'
5
+ import * as buf from 'lib0/buffer'
6
+ import * as rabin from 'lib0/hash/rabin'
7
+ import * as array from 'lib0/array'
8
+ import * as map from 'lib0/map'
9
+
10
+ import { iterateStructs, findIndexSS, iterateStructsWithoutSplits, tryGc } from './transaction-helpers.js'
11
+ import { UpdateEncoderV2, IdSetEncoderV2 } from './UpdateEncoder.js'
12
+ import { IdSetDecoderV2 } from './UpdateDecoder.js'
13
+
14
+ /**
15
+ * @typedef {{ inserts: IdSet, deletes: IdSet }} ContentIds
16
+ */
17
+
18
+ /**
19
+ * @typedef {{ inserts: IdMap<any>, deletes: IdMap<any> }} ContentMap
20
+ */
21
+
22
+ export class IdRange {
23
+ /**
24
+ * @param {number} clock
25
+ * @param {number} len
26
+ */
27
+ constructor (clock, len) {
28
+ /**
29
+ * @type {number}
30
+ */
31
+ this.clock = clock
32
+ /**
33
+ * @type {number}
34
+ */
35
+ this.len = len
36
+ }
37
+
38
+ /**
39
+ * @param {number} clock
40
+ * @param {number} len
41
+ */
42
+ copyWith (clock, len) {
43
+ return new IdRange(clock, len)
44
+ }
45
+
46
+ /**
47
+ * Helper method making this compatible with IdMap.
48
+ *
49
+ * @return {Array<ContentAttribute<any>>}
50
+ */
51
+ get attrs () {
52
+ return []
53
+ }
54
+ }
55
+
56
+ export class MaybeIdRange {
57
+ /**
58
+ * @param {number} clock
59
+ * @param {number} len
60
+ * @param {boolean} exists
61
+ */
62
+ constructor (clock, len, exists) {
63
+ /**
64
+ * @type {number}
65
+ */
66
+ this.clock = clock
67
+ /**
68
+ * @type {number}
69
+ */
70
+ this.len = len
71
+ /**
72
+ * @type {boolean}
73
+ */
74
+ this.exists = exists
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {number} clock
80
+ * @param {number} len
81
+ * @param {boolean} exists
82
+ * @return {MaybeIdRange}
83
+ */
84
+ export const createMaybeIdRange = (clock, len, exists) => new MaybeIdRange(clock, len, exists)
85
+
86
+ export class IdRanges {
87
+ /**
88
+ * @param {Array<IdRange>} ids
89
+ */
90
+ constructor (ids) {
91
+ this.sorted = false
92
+ /**
93
+ * A typical use-case for IdSet is to append data. We heavily optimize this case by allowing the
94
+ * last item to be mutated if it isn't used currently.
95
+ * This flag is true if the last item was exposed to the outside.
96
+ */
97
+ this._lastIsUsed = false
98
+ /**
99
+ * @private
100
+ */
101
+ this._ids = ids
102
+ }
103
+
104
+ copy () {
105
+ const cpy = new IdRanges(this._ids.slice())
106
+ cpy.sorted = this.sorted
107
+ this._lastIsUsed = true
108
+ return cpy
109
+ }
110
+
111
+ /**
112
+ * @param {number} clock
113
+ * @param {number} length
114
+ */
115
+ add (clock, length) {
116
+ const last = this._ids[this._ids.length - 1]
117
+ if (last != null && last.clock + last.len === clock) {
118
+ if (this._lastIsUsed) {
119
+ this._ids[this._ids.length - 1] = new IdRange(last.clock, last.len + length)
120
+ this._lastIsUsed = false
121
+ } else {
122
+ this._ids[this._ids.length - 1].len += length
123
+ }
124
+ } else {
125
+ this.sorted = false
126
+ this._ids.push(new IdRange(clock, length))
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Return the list of immutable id ranges, sorted and merged.
132
+ */
133
+ getIds () {
134
+ const ids = this._ids
135
+ this._lastIsUsed = true
136
+ if (!this.sorted) {
137
+ this.sorted = true
138
+ ids.sort((a, b) => a.clock - b.clock)
139
+ // merge items without filtering or splicing the array
140
+ // i is the current pointer
141
+ // j refers to the current insert position for the pointed item
142
+ // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
143
+ let i, j
144
+ for (i = 1, j = 1; i < ids.length; i++) {
145
+ const left = ids[j - 1]
146
+ const right = ids[i]
147
+ if (left.clock + left.len >= right.clock) {
148
+ const r = right.clock + right.len - left.clock
149
+ if (left.len < r) {
150
+ ids[j - 1] = new IdRange(left.clock, r)
151
+ }
152
+ } else if (left.len === 0) {
153
+ ids[j - 1] = right
154
+ } else {
155
+ if (j < i) {
156
+ ids[j] = right
157
+ }
158
+ j++
159
+ }
160
+ }
161
+ ids.length = ids[j - 1].len === 0 ? j - 1 : j
162
+ }
163
+ return ids
164
+ }
165
+ }
166
+
167
+ /**
168
+ * @implements {traits.EqualityTrait}
169
+ */
170
+ export class IdSet {
171
+ constructor () {
172
+ /**
173
+ * @type {Map<number,IdRanges>}
174
+ */
175
+ this.clients = new Map()
176
+ }
177
+
178
+ isEmpty () {
179
+ return this.clients.size === 0
180
+ }
181
+
182
+ /**
183
+ * @param {(idrange:IdRange, client:number) => void} f
184
+ */
185
+ forEach (f) {
186
+ this.clients.forEach((ranges, client) => {
187
+ ranges.getIds().forEach((range) => {
188
+ f(range, client)
189
+ })
190
+ })
191
+ }
192
+
193
+ /**
194
+ * @param {ID} id
195
+ * @return {boolean}
196
+ */
197
+ hasId (id) {
198
+ return this.has(id.client, id.clock)
199
+ }
200
+
201
+ /**
202
+ * @param {number} client
203
+ * @param {number} clock
204
+ */
205
+ has (client, clock) {
206
+ const dr = this.clients.get(client)
207
+ if (dr) {
208
+ return findIndexInIdRanges(dr.getIds(), clock) !== null
209
+ }
210
+ return false
211
+ }
212
+
213
+ /**
214
+ * Return slices of ids that exist in this idset.
215
+ *
216
+ * @param {number} client
217
+ * @param {number} clock
218
+ * @param {number} len
219
+ * @return {Array<MaybeIdRange>}
220
+ */
221
+ slice (client, clock, len) {
222
+ const dr = this.clients.get(client)
223
+ /**
224
+ * @type {Array<MaybeIdRange>}
225
+ */
226
+ const res = []
227
+ if (dr) {
228
+ /**
229
+ * @type {Array<IdRange>}
230
+ */
231
+ const ranges = dr.getIds()
232
+ let index = findRangeStartInIdRanges(ranges, clock)
233
+ if (index !== null) {
234
+ let prev = null
235
+ while (index < ranges.length) {
236
+ let r = ranges[index]
237
+ if (r.clock < clock) {
238
+ r = new IdRange(clock, r.len - (clock - r.clock))
239
+ }
240
+ if (r.clock + r.len > clock + len) {
241
+ r = new IdRange(r.clock, clock + len - r.clock)
242
+ }
243
+ if (r.len <= 0) break
244
+ const prevEnd = prev != null ? prev.clock + prev.len : clock
245
+ if (prevEnd < r.clock) {
246
+ res.push(createMaybeIdRange(prevEnd, r.clock - prevEnd, false))
247
+ }
248
+ prev = r
249
+ res.push(createMaybeIdRange(r.clock, r.len, true))
250
+ index++
251
+ }
252
+ }
253
+ }
254
+ if (res.length > 0) {
255
+ const last = res[res.length - 1]
256
+ const end = last.clock + last.len
257
+ if (end < clock + len) {
258
+ res.push(createMaybeIdRange(end, clock + len - end, false))
259
+ }
260
+ } else {
261
+ res.push(createMaybeIdRange(clock, len, false))
262
+ }
263
+ return res
264
+ }
265
+
266
+ /**
267
+ * @param {number} client
268
+ * @param {number} clock
269
+ * @param {number} len
270
+ */
271
+ add (client, clock, len) {
272
+ if (len === 0) return
273
+ const idRanges = this.clients.get(client)
274
+ if (idRanges) {
275
+ idRanges.add(clock, len)
276
+ } else {
277
+ this.clients.set(client, new IdRanges([new IdRange(clock, len)]))
278
+ }
279
+ }
280
+
281
+ /**
282
+ * @param {number} client
283
+ * @param {number} clock
284
+ * @param {number} len
285
+ */
286
+ delete (client, clock, len) {
287
+ _deleteRangeFromIdSet(this, client, clock, len)
288
+ }
289
+
290
+ /**
291
+ * @param {any} other
292
+ */
293
+ [traits.EqualityTraitSymbol] (other) {
294
+ return equalIdSets(this, other)
295
+ }
296
+ }
297
+
298
+ /**
299
+ * @param {IdSet} ds1
300
+ * @param {IdSet} ds2
301
+ */
302
+ export const equalIdSets = (ds1, ds2) => {
303
+ if (ds1.clients.size !== ds2.clients.size) return false
304
+ for (const [client, _deleteItems1] of ds1.clients.entries()) {
305
+ const deleteItems1 = _deleteItems1.getIds()
306
+ const deleteItems2 = ds2.clients.get(client)?.getIds()
307
+ if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false
308
+ for (let i = 0; i < deleteItems1.length; i++) {
309
+ const di1 = deleteItems1[i]
310
+ const di2 = deleteItems2[i]
311
+ if (di1.clock !== di2.clock || di1.len !== di2.len) {
312
+ return false
313
+ }
314
+ }
315
+ }
316
+ return true
317
+ }
318
+
319
+ /**
320
+ * @param {IdSet | IdMap<any>} set
321
+ * @param {number} client
322
+ * @param {number} clock
323
+ * @param {number} len
324
+ */
325
+ export const _deleteRangeFromIdSet = (set, client, clock, len) => {
326
+ const dr = set.clients.get(client)
327
+ if (dr && len > 0) {
328
+ const ids = dr.getIds()
329
+ let index = findRangeStartInIdRanges(ids, clock)
330
+ if (index != null) {
331
+ for (let r = ids[index]; index < ids.length && r.clock < clock + len; r = ids[++index]) {
332
+ if (r.clock < clock) {
333
+ ids[index] = r.copyWith(r.clock, clock - r.clock)
334
+ if (clock + len < r.clock + r.len) {
335
+ ids.splice(index + 1, 0, r.copyWith(clock + len, r.clock + r.len - clock - len))
336
+ }
337
+ } else if (clock + len < r.clock + r.len) {
338
+ // need to retain end
339
+ ids[index] = r.copyWith(clock + len, r.clock + r.len - clock - len)
340
+ } else if (ids.length === 1) {
341
+ set.clients.delete(client)
342
+ return
343
+ } else {
344
+ ids.splice(index--, 1)
345
+ }
346
+ }
347
+ }
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Iterate over all structs that are mentioned by the IdSet.
353
+ *
354
+ * @param {Transaction} transaction
355
+ * @param {IdSet} ds
356
+ * @param {function(GC|Item):void} f
357
+ *
358
+ * @function
359
+ */
360
+ export const iterateStructsByIdSet = (transaction, ds, f) =>
361
+ ds.clients.forEach((idRanges, clientid) => {
362
+ const ranges = idRanges.getIds()
363
+ const structs = /** @type {Array<GC|Item>} */ (transaction.doc.store.clients.get(clientid))
364
+ if (structs != null) {
365
+ for (let i = 0; i < ranges.length; i++) {
366
+ const del = ranges[i]
367
+ iterateStructs(transaction, structs, del.clock, del.len, f)
368
+ }
369
+ }
370
+ })
371
+
372
+ /**
373
+ * Garbage-collect the deleted content referenced by `ids` on `doc`.
374
+ *
375
+ * This is useful to retroactively reclaim memory on a document created with `gc: false`
376
+ * (e.g. once a snapshot, or an UndoManager StackItem, that referenced this content is no
377
+ * longer needed) - without enabling gc for the whole document. Ids that reference live
378
+ * (non-deleted) content, already-collected structs, items flagged with `keep`, or items
379
+ * rejected by `gcFilter` are skipped.
380
+ *
381
+ * @param {Doc} doc
382
+ * @param {IdSet} ids
383
+ * @param {function(Item):boolean} [gcFilter]
384
+ */
385
+ export const gcIdSet = (doc, ids, gcFilter = doc.gcFilter) =>
386
+ doc.transact(tr => {
387
+ // Split structs exactly at the IdSet boundaries so that only the referenced content is
388
+ // collected - an arbitrary IdSet need not align with struct boundaries.
389
+ iterateStructsByIdSet(tr, ids, () => {})
390
+ tryGc(tr, ids, gcFilter)
391
+ })
392
+
393
+ /**
394
+ * Iterate over all structs that are mentioned by the IdSet, without spliting the items.
395
+ *
396
+ * @param {StructStore} store
397
+ * @param {IdSet} ds
398
+ * @param {(struct: GC|Item|Skip, offset:number, len:number)=>void} f
399
+ */
400
+ export const iterateStructsByIdSetWithoutSplits = (store, ds, f) =>
401
+ ds.clients.forEach((idRanges, clientid) => {
402
+ const ranges = idRanges.getIds()
403
+ const structs = /** @type {Array<GC|Item>} */ (store.clients.get(clientid))
404
+ if (structs != null) {
405
+ const lastStruct = structs[structs.length - 1]
406
+ const nextClock = lastStruct.id.clock + lastStruct.length
407
+ for (let i = 0; i < ranges.length; i++) {
408
+ const del = ranges[i]
409
+ if (del.clock < nextClock) {
410
+ iterateStructsWithoutSplits(structs, del.clock, del.len, f)
411
+ }
412
+ }
413
+ }
414
+ })
415
+
416
+ /**
417
+ * @param {Array<IdRange>} dis
418
+ * @param {number} clock
419
+ * @return {number|null}
420
+ *
421
+ * @private
422
+ * @function
423
+ */
424
+ export const findIndexInIdRanges = (dis, clock) => {
425
+ let left = 0
426
+ let right = dis.length - 1
427
+ while (left <= right) {
428
+ const midindex = math.floor((left + right) / 2)
429
+ const mid = dis[midindex]
430
+ const midclock = mid.clock
431
+ if (midclock <= clock) {
432
+ if (clock < midclock + mid.len) {
433
+ return midindex
434
+ }
435
+ left = midindex + 1
436
+ } else {
437
+ right = midindex - 1
438
+ }
439
+ }
440
+ return null
441
+ }
442
+
443
+ /**
444
+ * Find the first range that contains clock or comes after clock.
445
+ *
446
+ * @param {Array<IdRange>} dis
447
+ * @param {number} clock
448
+ * @return {number|null}
449
+ *
450
+ * @private
451
+ * @function
452
+ */
453
+ export const findRangeStartInIdRanges = (dis, clock) => {
454
+ let left = 0
455
+ let right = dis.length - 1
456
+ while (left <= right) {
457
+ const midindex = math.floor((left + right) / 2)
458
+ const mid = dis[midindex]
459
+ const midclock = mid.clock
460
+ if (midclock <= clock) {
461
+ if (clock < midclock + mid.len) {
462
+ return midindex
463
+ }
464
+ left = midindex + 1
465
+ } else {
466
+ right = midindex - 1
467
+ }
468
+ }
469
+ return left < dis.length ? left : null
470
+ }
471
+
472
+ /**
473
+ * @param {Array<IdSet>} idSets
474
+ * @return {IdSet} A fresh IdSet
475
+ */
476
+ export const mergeIdSets = idSets => {
477
+ const merged = new IdSet()
478
+ for (let dssI = 0; dssI < idSets.length; dssI++) {
479
+ idSets[dssI].clients.forEach((rangesLeft, client) => {
480
+ if (!merged.clients.has(client)) {
481
+ // Write all missing keys from current ds and all following.
482
+ // If merged already contains `client` current ds has already been added.
483
+ const ids = rangesLeft.getIds().slice()
484
+ for (let i = dssI + 1; i < idSets.length; i++) {
485
+ const nextIds = idSets[i].clients.get(client)
486
+ if (nextIds) {
487
+ array.appendTo(ids, nextIds.getIds())
488
+ }
489
+ }
490
+ merged.clients.set(client, new IdRanges(ids))
491
+ }
492
+ })
493
+ }
494
+ return merged
495
+ }
496
+
497
+ /**
498
+ * @template {IdSet | IdMap<any>} S
499
+ * @param {S} dest
500
+ * @param {S} src
501
+ */
502
+ const _insertIntoIdSet = (dest, src) => {
503
+ src.clients.forEach((srcRanges, client) => {
504
+ const targetRanges = dest.clients.get(client)
505
+ if (targetRanges) {
506
+ array.appendTo(targetRanges.getIds(), srcRanges.getIds())
507
+ targetRanges.sorted = false
508
+ } else {
509
+ // order first
510
+ srcRanges.getIds()
511
+ dest.clients.set(client, /** @type {any} */ (srcRanges.copy()))
512
+ }
513
+ })
514
+ }
515
+
516
+ /**
517
+ * @param {IdSet} dest
518
+ * @param {IdSet} src
519
+ */
520
+ export const insertIntoIdSet = _insertIntoIdSet
521
+
522
+ /**
523
+ * @param {IdMap<any>} dest
524
+ * @param {IdMap<any>|IdSet} src
525
+ */
526
+ export const insertIntoIdMap = _insertIntoIdSet
527
+
528
+ /**
529
+ * @todo rename to excludeIdSet | excludeIdMap
530
+ *
531
+ * Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not
532
+ * in `exclude`.
533
+ *
534
+ * @template {IdSet | IdMap<any>} Set
535
+ * @param {Set} set
536
+ * @param {IdSet | IdMap<any>} exclude
537
+ * @return {Set}
538
+ */
539
+ export const _diffSet = (set, exclude) => {
540
+ /**
541
+ * @type {Set}
542
+ */
543
+ const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new IdMap())
544
+ const Ranges = set instanceof IdSet ? IdRanges : AttrRanges
545
+ set.clients.forEach((_setRanges, client) => {
546
+ /**
547
+ * @type {Array<IdRange>}
548
+ */
549
+ let resRanges = []
550
+ const _excludedRanges = exclude.clients.get(client)
551
+ const setRanges = _setRanges.getIds()
552
+ if (_excludedRanges == null) {
553
+ resRanges = setRanges.slice()
554
+ } else {
555
+ const excludedRanges = _excludedRanges.getIds()
556
+ let i = 0; let j = 0
557
+ let currRange = setRanges[0]
558
+ while (i < setRanges.length && j < excludedRanges.length) {
559
+ const e = excludedRanges[j]
560
+ if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item
561
+ if (currRange.len > 0) resRanges.push(currRange)
562
+ currRange = setRanges[++i]
563
+ } else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item
564
+ j++
565
+ } else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide)
566
+ const newClock = e.clock + e.len
567
+ const newLen = currRange.clock + currRange.len - newClock
568
+ if (newLen > 0) {
569
+ currRange = currRange.copyWith(newClock, newLen)
570
+ j++
571
+ } else {
572
+ // this item is completely overwritten. len=0. We can jump to the next range
573
+ currRange = setRanges[++i]
574
+ }
575
+ } else { // currRange.clock < e.clock -- range laps into exclude => adjust len
576
+ // beginning can't be empty, add it to the result
577
+ const nextLen = e.clock - currRange.clock
578
+ resRanges.push(currRange.copyWith(currRange.clock, nextLen))
579
+ // retain the remaining length after exclude in currRange
580
+ currRange = currRange.copyWith(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0))
581
+ if (currRange.len === 0) currRange = setRanges[++i]
582
+ else j++
583
+ }
584
+ }
585
+ if (currRange != null) {
586
+ resRanges.push(currRange)
587
+ }
588
+ i++
589
+ while (i < setRanges.length) {
590
+ resRanges.push(setRanges[i++])
591
+ }
592
+ }
593
+ // @ts-ignore
594
+ if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
595
+ })
596
+ return res
597
+ }
598
+
599
+ /**
600
+ * Remove all ranges from `exclude` from `idSet`. The result is a fresh IdSet containing all ranges from `idSet` that are not
601
+ * in `exclude`.
602
+ *
603
+ * @type {(idSet: IdSet, exclude: IdSet|IdMap<any>) => IdSet}
604
+ */
605
+ export const diffIdSet = _diffSet
606
+
607
+ /**
608
+ * @template {IdSet | IdMap<any>} SetA
609
+ * @template {IdSet | IdMap<any>} SetB
610
+ * @param {SetA} setA
611
+ * @param {SetB} setB
612
+ * @return {SetA extends IdMap<infer A> ? (SetB extends IdMap<infer B> ? IdMap<A | B> : IdMap<A>) : IdSet}
613
+ */
614
+ export const _intersectSets = (setA, setB) => {
615
+ /**
616
+ * @type {IdMap<any> | IdSet}
617
+ */
618
+ const res = /** @type {any } */ (setA instanceof IdSet ? new IdSet() : new IdMap())
619
+ const Ranges = setA instanceof IdSet ? IdRanges : AttrRanges
620
+ setA.clients.forEach((_aRanges, client) => {
621
+ /**
622
+ * @type {Array<IdRange>}
623
+ */
624
+ const resRanges = []
625
+ const _bRanges = setB.clients.get(client)
626
+ const aRanges = _aRanges.getIds()
627
+ if (_bRanges != null) {
628
+ const bRanges = _bRanges.getIds()
629
+ for (let a = 0, b = 0; a < aRanges.length && b < bRanges.length;) {
630
+ const aRange = aRanges[a]
631
+ const bRange = bRanges[b]
632
+ // construct overlap
633
+ const clock = math.max(aRange.clock, bRange.clock)
634
+ const len = math.min(aRange.len - (clock - aRange.clock), bRange.len - (clock - bRange.clock))
635
+ if (len > 0) {
636
+ resRanges.push(aRange instanceof AttrRange
637
+ ? new AttrRange(clock, len, /** @type {Array<any>} */ (aRange.attrs).concat(bRange.attrs))
638
+ : new IdRange(clock, len)
639
+ )
640
+ }
641
+ if (aRange.clock + aRange.len < bRange.clock + bRange.len) {
642
+ a++
643
+ } else {
644
+ b++
645
+ }
646
+ }
647
+ }
648
+ // @ts-ignore
649
+ if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
650
+ })
651
+ return /** @type {any} */ (res)
652
+ }
653
+
654
+ export const intersectSets = _intersectSets
655
+
656
+ export const createIdSet = () => new IdSet()
657
+
658
+ /**
659
+ * @param {StructStore} ss
660
+ * @return {IdSet}
661
+ *
662
+ * @private
663
+ * @function
664
+ */
665
+ export const createDeleteSetFromStructStore = ss => {
666
+ const ds = createIdSet()
667
+ ss.clients.forEach((structs, client) => {
668
+ /**
669
+ * @type {Array<IdRange>}
670
+ */
671
+ const dsitems = []
672
+ for (let i = 0; i < structs.length; i++) {
673
+ const struct = structs[i]
674
+ if (struct.deleted) {
675
+ const clock = struct.id.clock
676
+ let len = struct.length
677
+ if (i + 1 < structs.length) {
678
+ for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) {
679
+ len += next.length
680
+ }
681
+ }
682
+ dsitems.push(new IdRange(clock, len))
683
+ }
684
+ }
685
+ if (dsitems.length > 0) {
686
+ ds.clients.set(client, new IdRanges(dsitems))
687
+ }
688
+ })
689
+ return ds
690
+ }
691
+
692
+ /**
693
+ * @param {Array<GC | Item | Skip>} structs
694
+ * @param {boolean} filterDeleted
695
+ *
696
+ */
697
+ export const _createInsertSliceFromStructs = (structs, filterDeleted) => {
698
+ /**
699
+ * @type {Array<IdRange>}
700
+ */
701
+ const iditems = []
702
+ for (let i = 0; i < structs.length; i++) {
703
+ const struct = structs[i]
704
+ if (!(filterDeleted && struct.deleted)) {
705
+ const clock = struct.id.clock
706
+ let len = struct.length
707
+ if (i + 1 < structs.length) {
708
+ // eslint-disable-next-line
709
+ for (let next = structs[i + 1]; i + 1 < structs.length && !(filterDeleted && next.deleted); next = structs[++i + 1]) {
710
+ len += next.length
711
+ }
712
+ }
713
+ iditems.push(new IdRange(clock, len))
714
+ }
715
+ }
716
+ return iditems
717
+ }
718
+
719
+ /**
720
+ * @param {StructStore} ss
721
+ * @param {boolean} filterDeleted
722
+ */
723
+ export const createInsertSetFromStructStore = (ss, filterDeleted) => {
724
+ const idset = createIdSet()
725
+ ss.clients.forEach((structs, client) => {
726
+ const iditems = _createInsertSliceFromStructs(structs, filterDeleted)
727
+ if (iditems.length !== 0) {
728
+ idset.clients.set(client, new IdRanges(iditems))
729
+ }
730
+ })
731
+ return idset
732
+ }
733
+
734
+ /**
735
+ * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
736
+ * @param {IdSet} idSet
737
+ *
738
+ * @private
739
+ * @function
740
+ */
741
+ export const writeIdSet = (encoder, idSet) => {
742
+ encoding.writeVarUint(encoder.restEncoder, idSet.clients.size)
743
+ // Ensure that the delete set is written in a deterministic order
744
+ array.from(idSet.clients.entries())
745
+ .sort((a, b) => b[0] - a[0])
746
+ .forEach(([client, _idRanges]) => {
747
+ const idRanges = _idRanges.getIds()
748
+ encoder.resetIdSetCurVal()
749
+ encoding.writeVarUint(encoder.restEncoder, client)
750
+ const len = idRanges.length
751
+ encoding.writeVarUint(encoder.restEncoder, len)
752
+ for (let i = 0; i < len; i++) {
753
+ const item = idRanges[i]
754
+ encoder.writeIdSetClock(item.clock)
755
+ encoder.writeIdSetLen(item.len)
756
+ }
757
+ })
758
+ }
759
+
760
+ /**
761
+ * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
762
+ * @return {IdSet}
763
+ *
764
+ * @private
765
+ * @function
766
+ */
767
+ export const readIdSet = decoder => {
768
+ const ds = new IdSet()
769
+ const numClients = decoding.readVarUint(decoder.restDecoder)
770
+ for (let i = 0; i < numClients; i++) {
771
+ decoder.resetDsCurVal()
772
+ const client = decoding.readVarUint(decoder.restDecoder)
773
+ const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
774
+ if (numberOfDeletes > 0) {
775
+ /**
776
+ * @type {Array<IdRange>}
777
+ */
778
+ const dsRanges = []
779
+ for (let i = 0; i < numberOfDeletes; i++) {
780
+ dsRanges.push(new IdRange(decoder.readDsClock(), decoder.readDsLen()))
781
+ }
782
+ ds.clients.set(client, new IdRanges(dsRanges))
783
+ }
784
+ }
785
+ return ds
786
+ }
787
+
788
+ /**
789
+ * @param {IdSet} idSet
790
+ * @return {Uint8Array<ArrayBuffer>}
791
+ */
792
+ export const encodeIdSet = idSet => {
793
+ const encoder = new IdSetEncoderV2()
794
+ writeIdSet(encoder, idSet)
795
+ return encoder.toUint8Array()
796
+ }
797
+
798
+ /**
799
+ * @param {Uint8Array} data
800
+ * @return {IdSet}
801
+ */
802
+ export const decodeIdSet = data => readIdSet(new IdSetDecoderV2(decoding.createDecoder(data)))
803
+
804
+ /**
805
+ * @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array()..
806
+ */
807
+
808
+ /**
809
+ * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
810
+ * @param {Transaction} transaction
811
+ * @param {StructStore} store
812
+ * @return {Uint8Array<ArrayBuffer>|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully.
813
+ *
814
+ * @private
815
+ * @function
816
+ */
817
+ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
818
+ const unappliedDS = new IdSet()
819
+ const numClients = decoding.readVarUint(decoder.restDecoder)
820
+ for (let i = 0; i < numClients; i++) {
821
+ decoder.resetDsCurVal()
822
+ const client = decoding.readVarUint(decoder.restDecoder)
823
+ const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
824
+ const structs = store.clients.get(client) || []
825
+ const state = store.getClock(client)
826
+ for (let i = 0; i < numberOfDeletes; i++) {
827
+ const clock = decoder.readDsClock()
828
+ const clockEnd = clock + decoder.readDsLen()
829
+ if (clock < state) {
830
+ if (state < clockEnd) {
831
+ unappliedDS.add(client, state, clockEnd - state)
832
+ }
833
+ let index = findIndexSS(structs, clock)
834
+ /**
835
+ * We can ignore the case of GC and Delete structs, because we are going to skip them
836
+ * @type {Item | GC | Skip}
837
+ */
838
+ let struct = structs[index]
839
+ // split the first item if necessary
840
+ if (!struct.deleted && struct.id.clock < clock && struct.isItem) {
841
+ // increment index, we now want to use the next struct
842
+ structs.splice(++index, 0, /** @type {Item} */ (struct).split(transaction, clock - struct.id.clock))
843
+ }
844
+ while (index < structs.length) {
845
+ // @ts-ignore
846
+ struct = structs[index++]
847
+ if (struct.id.clock < clockEnd) {
848
+ if (!struct.deleted) {
849
+ if (struct.isItem) {
850
+ if (clockEnd < struct.id.clock + struct.length) {
851
+ structs.splice(index, 0, /** @type {Item} */ (struct).split(transaction, clockEnd - struct.id.clock))
852
+ }
853
+ struct.delete(transaction)
854
+ } else { // is a Skip - add range to unappliedDS
855
+ const c = math.max(struct.id.clock, clock)
856
+ unappliedDS.add(client, c, math.min(struct.length, clockEnd - c))
857
+ }
858
+ }
859
+ } else {
860
+ break
861
+ }
862
+ }
863
+ } else {
864
+ unappliedDS.add(client, clock, clockEnd - clock)
865
+ }
866
+ }
867
+ }
868
+ if (unappliedDS.clients.size > 0) {
869
+ const ds = new UpdateEncoderV2()
870
+ encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs
871
+ writeIdSet(ds, unappliedDS)
872
+ return ds.toUint8Array()
873
+ }
874
+ return null
875
+ }
876
+
877
+ /**
878
+ * @template Attrs
879
+ */
880
+ export class AttrRange {
881
+ /**
882
+ * @param {number} clock
883
+ * @param {number} len
884
+ * @param {Array<ContentAttribute<Attrs>>} attrs
885
+ */
886
+ constructor (clock, len, attrs) {
887
+ /**
888
+ * @readonly
889
+ */
890
+ this.clock = clock
891
+ /**
892
+ * @readonly
893
+ */
894
+ this.len = len
895
+ /**
896
+ * @readonly
897
+ */
898
+ this.attrs = attrs
899
+ }
900
+
901
+ /**
902
+ * @param {number} clock
903
+ * @param {number} len
904
+ */
905
+ copyWith (clock, len) {
906
+ return new AttrRange(clock, len, this.attrs)
907
+ }
908
+ }
909
+
910
+ /**
911
+ * @todo rename this to `Attribute`
912
+ * @template V
913
+ */
914
+ export class ContentAttribute {
915
+ /**
916
+ * @param {string} name
917
+ * @param {V} val
918
+ */
919
+ constructor (name, val) {
920
+ this.name = name
921
+ this.val = val
922
+ }
923
+
924
+ hash () {
925
+ const encoder = encoding.createEncoder()
926
+ encoding.writeVarString(encoder, this.name)
927
+ encoding.writeAny(encoder, /** @type {any} */ (this.val))
928
+ return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder)))
929
+ }
930
+ }
931
+
932
+ /**
933
+ * @template V
934
+ * @param {string} name
935
+ * @param {V} val
936
+ * @return {ContentAttribute<V>}
937
+ */
938
+ export const createContentAttribute = (name, val) => new ContentAttribute(name, val)
939
+
940
+ /**
941
+ * @template Attrs
942
+ * @typedef {{ clock: number, len: number, attrs: Array<ContentAttribute<Attrs>>? }} MaybeAttrRange
943
+ */
944
+
945
+ /**
946
+ * @template Attrs
947
+ *
948
+ * @param {number} clock
949
+ * @param {number} len
950
+ * @param {Array<ContentAttribute<Attrs>>?} attrs
951
+ * @return {MaybeAttrRange<Attrs>}
952
+ */
953
+ export const createMaybeAttrRange = (clock, len, attrs) => new AttrRange(clock, len, /** @type {any} */ (attrs))
954
+
955
+ /**
956
+ * @template T
957
+ * @param {Array<T>} a
958
+ * @param {Array<T>} b
959
+ */
960
+ const idmapAttrRangeJoin = (a, b) => a.concat(b.filter(attr => !idmapAttrsHas(a, attr)))
961
+
962
+ /**
963
+ * Whenever this is instantiated, it must receive a fresh array of ops, not something copied.
964
+ *
965
+ * @template Attrs
966
+ */
967
+ export class AttrRanges {
968
+ /**
969
+ * @param {Array<AttrRange<Attrs>>} ids
970
+ */
971
+ constructor (ids) {
972
+ this.sorted = false
973
+ /**
974
+ * @private
975
+ */
976
+ this._ids = ids
977
+ }
978
+
979
+ copy () {
980
+ const cpy = new AttrRanges(this._ids.slice())
981
+ cpy.sorted = this.sorted
982
+ return cpy
983
+ }
984
+
985
+ /**
986
+ * @param {number} clock
987
+ * @param {number} length
988
+ * @param {Array<ContentAttribute<Attrs>>} attrs
989
+ */
990
+ add (clock, length, attrs) {
991
+ if (length === 0) return
992
+ this.sorted = false
993
+ this._ids.push(new AttrRange(clock, length, attrs))
994
+ }
995
+
996
+ /**
997
+ * Return the list of id ranges, sorted and merged.
998
+ */
999
+ getIds () {
1000
+ const ids = this._ids
1001
+ if (!this.sorted) {
1002
+ this.sorted = true
1003
+ ids.sort((a, b) => a.clock - b.clock)
1004
+ /**
1005
+ * algorithm thoughts:
1006
+ * - sort (by clock AND by length), bigger length is to the right (or not, we can't make
1007
+ * assumptions abouth length after long length has been split)
1008
+ * -- maybe better: sort by clock+length. Then split items from right to left. This way, items are always
1009
+ * in the right order. But I also need to swap if left items is smaller after split
1010
+ * --- thought: there is no way to go around swapping. Unless, for each item from left to
1011
+ * right, when I have to split because one of the look-ahead items is overlapping, i split
1012
+ * it and merge the attributes into the following ones (that I also need to split). Best is
1013
+ * probably left to right with lookahead.
1014
+ * - left to right, split overlapping items so that we can make the assumption that either an
1015
+ * item is overlapping with the next 1-on-1 or it is not overlapping at all (when splitting,
1016
+ * we can already incorporate the attributes)
1017
+ * -- better: for each item, go left to right and add own attributes to overlapping items.
1018
+ * Split them if necessary. After split, i must insert the retainer at a valid position.
1019
+ * - merge items if neighbor has same attributes
1020
+ */
1021
+ for (let i = 0; i < ids.length - 1;) {
1022
+ const range = ids[i]
1023
+ const nextRange = ids[i + 1]
1024
+ // find out how to split range. it must match with next range.
1025
+ // 1) we have space. Split if necessary.
1026
+ // 2) concat attributes in range to the next range. Split range and splice the remainder at
1027
+ // the correct position.
1028
+ if (range.clock < nextRange.clock) { // might need to split range
1029
+ if (range.clock + range.len > nextRange.clock) {
1030
+ // is overlapping
1031
+ const diff = nextRange.clock - range.clock
1032
+ ids[i] = new AttrRange(range.clock, diff, range.attrs)
1033
+ ids.splice(i + 1, 0, new AttrRange(nextRange.clock, range.len - diff, range.attrs))
1034
+ }
1035
+ i++
1036
+ continue
1037
+ }
1038
+ // now we know that range.clock === nextRange.clock
1039
+ // merge range with nextRange
1040
+ const largerRange = range.len > nextRange.len ? range : nextRange
1041
+ const smallerLen = range.len < nextRange.len ? range.len : nextRange.len
1042
+ ids[i] = new AttrRange(range.clock, smallerLen, idmapAttrRangeJoin(range.attrs, nextRange.attrs))
1043
+ if (range.len === nextRange.len) {
1044
+ ids.splice(i + 1, 1)
1045
+ } else {
1046
+ ids[i + 1] = new AttrRange(range.clock + smallerLen, largerRange.len - smallerLen, largerRange.attrs)
1047
+ array.bubblesortItem(ids, i + 1, (a, b) => a.clock - b.clock)
1048
+ }
1049
+ if (smallerLen === 0) i++
1050
+ }
1051
+ while (ids.length > 0 && ids[0].len === 0) {
1052
+ ids.splice(0, 1)
1053
+ }
1054
+ // merge items without filtering or splicing the array.
1055
+ // i is the current pointer
1056
+ // j refers to the current insert position for the pointed item
1057
+ // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
1058
+ let i, j
1059
+ for (i = 1, j = 1; i < ids.length; i++) {
1060
+ const left = ids[j - 1]
1061
+ const right = ids[i]
1062
+ if (left.clock + left.len === right.clock && idmapAttrsEqual(left.attrs, right.attrs)) {
1063
+ ids[j - 1] = new AttrRange(left.clock, left.len + right.len, left.attrs)
1064
+ } else if (right.len !== 0) {
1065
+ if (j < i) {
1066
+ ids[j] = right
1067
+ }
1068
+ j++
1069
+ }
1070
+ }
1071
+ ids.length = ids.length === 0 ? 0 : (ids[j - 1].len === 0 ? j - 1 : j)
1072
+ }
1073
+ return ids
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * @template Attrs
1079
+ */
1080
+ export class IdMap {
1081
+ constructor () {
1082
+ /**
1083
+ * @type {Map<number,AttrRanges<Attrs>>}
1084
+ */
1085
+ this.clients = new Map()
1086
+ /**
1087
+ * @type {Map<string, ContentAttribute<Attrs>>}
1088
+ */
1089
+ this.attrsH = new Map()
1090
+ /**
1091
+ * @type {Set<ContentAttribute<Attrs>>}
1092
+ */
1093
+ this.attrs = new Set()
1094
+ }
1095
+
1096
+ /**
1097
+ * @param {(attrRange:AttrRange<Attrs>, client:number) => void} f
1098
+ */
1099
+ forEach (f) {
1100
+ this.clients.forEach((ranges, client) => {
1101
+ ranges.getIds().forEach((range) => {
1102
+ f(range, client)
1103
+ })
1104
+ })
1105
+ }
1106
+
1107
+ isEmpty () {
1108
+ return this.clients.size === 0
1109
+ }
1110
+
1111
+ /**
1112
+ * @param {ID} id
1113
+ * @return {boolean}
1114
+ */
1115
+ hasId (id) {
1116
+ return this.has(id.client, id.clock)
1117
+ }
1118
+
1119
+ /**
1120
+ * @param {number} client
1121
+ * @param {number} clock
1122
+ * @return {boolean}
1123
+ */
1124
+ has (client, clock) {
1125
+ const dr = this.clients.get(client)
1126
+ if (dr) {
1127
+ return findIndexInIdRanges(dr.getIds(), clock) !== null
1128
+ }
1129
+ return false
1130
+ }
1131
+
1132
+ /**
1133
+ * Return attributions for a slice of ids.
1134
+ *
1135
+ * @param {ID} id
1136
+ * @param {number} len
1137
+ * @return {Array<MaybeAttrRange<Attrs>>}
1138
+ */
1139
+ sliceId (id, len) {
1140
+ return this.slice(id.client, id.clock, len)
1141
+ }
1142
+
1143
+ /**
1144
+ * Return attributions for a slice of ids.
1145
+ *
1146
+ * @param {number} client
1147
+ * @param {number} clock
1148
+ * @param {number} len
1149
+ * @return {Array<MaybeAttrRange<Attrs>>}
1150
+ */
1151
+ slice (client, clock, len) {
1152
+ const dr = this.clients.get(client)
1153
+ /**
1154
+ * @type {Array<MaybeAttrRange<Attrs>>}
1155
+ */
1156
+ const res = []
1157
+ if (dr) {
1158
+ /**
1159
+ * @type {Array<AttrRange<Attrs>>}
1160
+ */
1161
+ const ranges = dr.getIds()
1162
+ let index = findRangeStartInIdRanges(ranges, clock)
1163
+ if (index !== null) {
1164
+ let prev = null
1165
+ while (index < ranges.length) {
1166
+ let r = ranges[index]
1167
+ if (r.clock < clock) {
1168
+ r = new AttrRange(clock, r.len - (clock - r.clock), r.attrs)
1169
+ }
1170
+ if (r.clock + r.len > clock + len) {
1171
+ r = new AttrRange(r.clock, clock + len - r.clock, r.attrs)
1172
+ }
1173
+ if (r.len <= 0) break
1174
+ const prevEnd = prev != null ? prev.clock + prev.len : clock
1175
+ if (prevEnd < r.clock) {
1176
+ res.push(createMaybeAttrRange(prevEnd, r.clock - prevEnd, null))
1177
+ }
1178
+ prev = r
1179
+ res.push(r)
1180
+ index++
1181
+ }
1182
+ }
1183
+ }
1184
+ if (res.length > 0) {
1185
+ const last = res[res.length - 1]
1186
+ const end = last.clock + last.len
1187
+ if (end < clock + len) {
1188
+ res.push(createMaybeAttrRange(end, clock + len - end, null))
1189
+ }
1190
+ } else {
1191
+ res.push(createMaybeAttrRange(clock, len, null))
1192
+ }
1193
+ return res
1194
+ }
1195
+
1196
+ /**
1197
+ * @param {number} client
1198
+ * @param {number} clock
1199
+ * @param {number} len
1200
+ * @param {Array<ContentAttribute<Attrs>>} attrs
1201
+ */
1202
+ add (client, clock, len, attrs) {
1203
+ if (len === 0) return
1204
+ attrs = _ensureAttrs(this, attrs)
1205
+ const ranges = this.clients.get(client)
1206
+ if (ranges == null) {
1207
+ this.clients.set(client, new AttrRanges([new AttrRange(clock, len, attrs)]))
1208
+ } else {
1209
+ ranges.add(clock, len, attrs)
1210
+ }
1211
+ }
1212
+
1213
+ /**
1214
+ * @param {number} client
1215
+ * @param {number} clock
1216
+ * @param {number} len
1217
+ */
1218
+ delete (client, clock, len) {
1219
+ _deleteRangeFromIdSet(this, client, clock, len)
1220
+ }
1221
+ }
1222
+
1223
+ /**
1224
+ * @template T
1225
+ * @param {Array<T>} attrs
1226
+ * @param {T} attr
1227
+ *
1228
+ */
1229
+ const idmapAttrsHas = (attrs, attr) => attrs.find(a => a === attr)
1230
+
1231
+ /**
1232
+ * @template T
1233
+ * @param {Array<T>} a
1234
+ * @param {Array<T>} b
1235
+ */
1236
+ export const idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => idmapAttrsHas(b, v))
1237
+
1238
+ /**
1239
+ * Merge multiple idmaps. Ensures that there are no redundant attribution definitions (two
1240
+ * Attributions that describe the same thing).
1241
+ *
1242
+ * @template T
1243
+ * @param {Array<IdMap<T>>} ams
1244
+ * @return {IdMap<T>} A fresh IdSet
1245
+ */
1246
+ export const mergeIdMaps = ams => {
1247
+ /**
1248
+ * Maps attribution to the attribution of the merged idmap.
1249
+ *
1250
+ * @type {Map<ContentAttribute<any>,ContentAttribute<any>>}
1251
+ */
1252
+ const attrMapper = new Map()
1253
+ const merged = createIdMap()
1254
+ for (let amsI = 0; amsI < ams.length; amsI++) {
1255
+ ams[amsI].clients.forEach((rangesLeft, client) => {
1256
+ if (!merged.clients.has(client)) {
1257
+ // Write all missing keys from current set and all following.
1258
+ // If merged already contains `client` current ds has already been added.
1259
+ let ids = rangesLeft.getIds().slice()
1260
+ for (let i = amsI + 1; i < ams.length; i++) {
1261
+ const nextIds = ams[i].clients.get(client)
1262
+ if (nextIds) {
1263
+ array.appendTo(ids, nextIds.getIds())
1264
+ }
1265
+ }
1266
+ ids = ids.map(id => new AttrRange(id.clock, id.len, id.attrs.map(attr =>
1267
+ map.setIfUndefined(attrMapper, attr, () =>
1268
+ _ensureAttrs(merged, [attr])[0]
1269
+ )
1270
+ )))
1271
+ merged.clients.set(client, new AttrRanges(ids))
1272
+ }
1273
+ })
1274
+ }
1275
+ return merged
1276
+ }
1277
+
1278
+ /**
1279
+ * @param {IdSet} idset
1280
+ * @param {Array<ContentAttribute<any>>} attrs
1281
+ */
1282
+ export const createIdMapFromIdSet = (idset, attrs) => {
1283
+ const idmap = createIdMap()
1284
+ // map attrs to idmap
1285
+ attrs = _ensureAttrs(idmap, attrs)
1286
+ // filter out duplicates
1287
+ /**
1288
+ * @type {Array<ContentAttribute<any>>}
1289
+ */
1290
+ const checkedAttrs = []
1291
+ attrs.forEach(attr => {
1292
+ if (!idmapAttrsHas(checkedAttrs, attr)) {
1293
+ checkedAttrs.push(attr)
1294
+ }
1295
+ })
1296
+ idset.clients.forEach((ranges, client) => {
1297
+ const attrRanges = new AttrRanges(ranges.getIds().map(range => new AttrRange(range.clock, range.len, checkedAttrs)))
1298
+ attrRanges.sorted = true // is sorted because idset is sorted
1299
+ idmap.clients.set(client, attrRanges)
1300
+ })
1301
+ return idmap
1302
+ }
1303
+
1304
+ /**
1305
+ * Create an IdSet from an IdMap by stripping the attributes.
1306
+ *
1307
+ * @param {IdMap<any>} idmap
1308
+ * @return {IdSet}
1309
+ */
1310
+ export const createIdSetFromIdMap = idmap => {
1311
+ const idset = createIdSet()
1312
+ idmap.clients.forEach((ranges, client) => {
1313
+ const idRanges = new IdRanges([])
1314
+ ranges.getIds().forEach(range => idRanges.add(range.clock, range.len))
1315
+ idset.clients.set(client, idRanges)
1316
+ })
1317
+ return idset
1318
+ }
1319
+
1320
+ /**
1321
+ * Efficiently encodes IdMap to a binary form. Ensures that information is de-duplicated when
1322
+ * written. Attribute.names are referenced by id. Attributes themselfs are also referenced by id.
1323
+ *
1324
+ * @template Attr
1325
+ * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
1326
+ * @param {IdMap<Attr>} idmap
1327
+ *
1328
+ * @private
1329
+ * @function
1330
+ */
1331
+ export const writeIdMap = (encoder, idmap) => {
1332
+ encoding.writeVarUint(encoder.restEncoder, idmap.clients.size)
1333
+ let lastWrittenClientId = 0
1334
+ /**
1335
+ * @type {Map<ContentAttribute<Attr>, number>}
1336
+ */
1337
+ const visitedAttributions = map.create()
1338
+ /**
1339
+ * @type {Map<string, number>}
1340
+ */
1341
+ const visitedAttrNames = map.create()
1342
+ // Ensure that the ids are written in a deterministic order (smaller clientids first)
1343
+ array.from(idmap.clients.entries())
1344
+ .sort((a, b) => a[0] - b[0])
1345
+ .forEach(([client, _idRanges]) => {
1346
+ const attrRanges = _idRanges.getIds()
1347
+ encoder.resetIdSetCurVal()
1348
+ const diff = client - lastWrittenClientId
1349
+ encoding.writeVarUint(encoder.restEncoder, diff)
1350
+ lastWrittenClientId = client
1351
+ const len = attrRanges.length
1352
+ encoding.writeVarUint(encoder.restEncoder, len)
1353
+ for (let i = 0; i < len; i++) {
1354
+ const item = attrRanges[i]
1355
+ const attrs = item.attrs
1356
+ const attrLen = attrs.length
1357
+ encoder.writeIdSetClock(item.clock)
1358
+ encoder.writeIdSetLen(item.len)
1359
+ encoding.writeVarUint(encoder.restEncoder, attrLen)
1360
+ for (let j = 0; j < attrLen; j++) {
1361
+ const attr = attrs[j]
1362
+ const attrId = visitedAttributions.get(attr)
1363
+ if (attrId != null) {
1364
+ encoding.writeVarUint(encoder.restEncoder, attrId)
1365
+ } else {
1366
+ const newAttrId = visitedAttributions.size
1367
+ visitedAttributions.set(attr, newAttrId)
1368
+ encoding.writeVarUint(encoder.restEncoder, newAttrId)
1369
+ const attrNameId = visitedAttrNames.get(attr.name)
1370
+ // write attr.name
1371
+ if (attrNameId != null) {
1372
+ encoding.writeVarUint(encoder.restEncoder, attrNameId)
1373
+ } else {
1374
+ const newAttrNameId = visitedAttrNames.size
1375
+ encoding.writeVarUint(encoder.restEncoder, newAttrNameId)
1376
+ encoding.writeVarString(encoder.restEncoder, attr.name)
1377
+ visitedAttrNames.set(attr.name, newAttrNameId)
1378
+ }
1379
+ encoding.writeAny(encoder.restEncoder, /** @type {any} */ (attr.val))
1380
+ }
1381
+ }
1382
+ }
1383
+ })
1384
+ }
1385
+
1386
+ /**
1387
+ * @param {IdMap<any>} idmap
1388
+ */
1389
+ export const encodeIdMap = idmap => {
1390
+ const encoder = new IdSetEncoderV2()
1391
+ writeIdMap(encoder, idmap)
1392
+ return encoder.toUint8Array()
1393
+ }
1394
+
1395
+ /**
1396
+ * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
1397
+ * @return {IdMap<any>}
1398
+ *
1399
+ * @private
1400
+ * @function
1401
+ */
1402
+ export const readIdMap = decoder => {
1403
+ const idmap = new IdMap()
1404
+ const numClients = decoding.readVarUint(decoder.restDecoder)
1405
+ /**
1406
+ * @type {Array<ContentAttribute<any>>}
1407
+ */
1408
+ const visitedAttributions = []
1409
+ /**
1410
+ * @type {Array<string>}
1411
+ */
1412
+ const visitedAttrNames = []
1413
+ let lastClientId = 0
1414
+ for (let i = 0; i < numClients; i++) {
1415
+ decoder.resetDsCurVal()
1416
+ const client = lastClientId + decoding.readVarUint(decoder.restDecoder)
1417
+ lastClientId = client
1418
+ const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
1419
+ /**
1420
+ * @type {Array<AttrRange<any>>}
1421
+ */
1422
+ const attrRanges = []
1423
+ for (let i = 0; i < numberOfDeletes; i++) {
1424
+ const rangeClock = decoder.readDsClock()
1425
+ const rangeLen = decoder.readDsLen()
1426
+ /**
1427
+ * @type {Array<ContentAttribute<any>>}
1428
+ */
1429
+ const attrs = []
1430
+ const attrsLen = decoding.readVarUint(decoder.restDecoder)
1431
+ for (let j = 0; j < attrsLen; j++) {
1432
+ const attrId = decoding.readVarUint(decoder.restDecoder)
1433
+ if (attrId >= visitedAttributions.length) {
1434
+ // attrId not known yet
1435
+ const attrNameId = decoding.readVarUint(decoder.restDecoder)
1436
+ if (attrNameId >= visitedAttrNames.length) {
1437
+ visitedAttrNames.push(decoding.readVarString(decoder.restDecoder))
1438
+ }
1439
+ visitedAttributions.push(new ContentAttribute(visitedAttrNames[attrNameId], decoding.readAny(decoder.restDecoder)))
1440
+ }
1441
+ attrs.push(visitedAttributions[attrId])
1442
+ }
1443
+ attrRanges.push(new AttrRange(rangeClock, rangeLen, attrs))
1444
+ }
1445
+ idmap.clients.set(client, new AttrRanges(attrRanges))
1446
+ }
1447
+ visitedAttributions.forEach(attr => {
1448
+ idmap.attrs.add(attr)
1449
+ idmap.attrsH.set(attr.hash(), attr)
1450
+ })
1451
+ return idmap
1452
+ }
1453
+
1454
+ /**
1455
+ * @param {Uint8Array} data
1456
+ * @return {IdMap<any>}
1457
+ */
1458
+ export const decodeIdMap = data => readIdMap(new IdSetDecoderV2(decoding.createDecoder(data)))
1459
+
1460
+ /**
1461
+ * @template Attrs
1462
+ * @param {IdMap<Attrs>} idmap
1463
+ * @param {Array<ContentAttribute<Attrs>>} attrs
1464
+ * @return {Array<ContentAttribute<Attrs>>}
1465
+ */
1466
+ const _ensureAttrs = (idmap, attrs) => attrs.map(attr =>
1467
+ idmap.attrs.has(attr)
1468
+ ? attr
1469
+ : map.setIfUndefined(idmap.attrsH, attr.hash(), () => {
1470
+ idmap.attrs.add(attr)
1471
+ return attr
1472
+ }))
1473
+
1474
+ export const createIdMap = () => new IdMap()
1475
+
1476
+ /**
1477
+ * Remove all ranges from `exclude` from `ds`. The result is a fresh IdMap containing all ranges from `idSet` that are not
1478
+ * in `exclude`.
1479
+ *
1480
+ * @todo this should be called "excludeIdMap"
1481
+ *
1482
+ * @template {IdMap<any>} ISet
1483
+ * @param {ISet} set
1484
+ * @param {IdSet | IdMap<any>} exclude
1485
+ * @return {ISet}
1486
+ */
1487
+ export const diffIdMap = (set, exclude) => {
1488
+ const diffed = _diffSet(set, exclude)
1489
+ diffed.attrs = set.attrs
1490
+ diffed.attrsH = set.attrsH
1491
+ return diffed
1492
+ }
1493
+
1494
+ export const intersectMaps = _intersectSets
1495
+
1496
+ /**
1497
+ * Filter attributes in an IdMap based on a predicate function.
1498
+ * Returns a new IdMap containing idranges that match the predicate.
1499
+ *
1500
+ * @template Attrs
1501
+ * @param {IdMap<Attrs>} idmap
1502
+ * @param {(attr: Array<ContentAttribute<Attrs>>) => boolean} predicate
1503
+ * @return {IdMap<Attrs>}
1504
+ */
1505
+ export const filterIdMap = (idmap, predicate) => {
1506
+ const filtered = createIdMap()
1507
+ idmap.clients.forEach((ranges, client) => {
1508
+ /**
1509
+ * @type {Array<AttrRange<Attrs>>}
1510
+ */
1511
+ const attrRanges = []
1512
+ ranges.getIds().forEach((range) => {
1513
+ if (predicate(range.attrs)) {
1514
+ const rangeCpy = range.copyWith(range.clock, range.len)
1515
+ attrRanges.push(rangeCpy)
1516
+ rangeCpy.attrs.forEach(attr => {
1517
+ filtered.attrs.add(attr)
1518
+ filtered.attrsH.set(attr.hash(), attr)
1519
+ })
1520
+ }
1521
+ })
1522
+ if (attrRanges.length > 0) {
1523
+ filtered.clients.set(client, new AttrRanges(attrRanges))
1524
+ }
1525
+ })
1526
+ return filtered
1527
+ }