@y/y 14.0.0-19 → 14.0.0-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.
- package/README.md +7 -5
- package/dist/src/index.d.ts +2 -1
- package/dist/src/internals.d.ts +2 -9
- package/dist/src/structs/ContentType.d.ts +6 -12
- package/dist/src/structs/ContentType.d.ts.map +1 -1
- package/dist/src/structs/Item.d.ts +5 -6
- package/dist/src/structs/Item.d.ts.map +1 -1
- package/dist/src/utils/AttributionManager.d.ts +16 -14
- package/dist/src/utils/AttributionManager.d.ts.map +1 -1
- package/dist/src/utils/Doc.d.ts +7 -70
- package/dist/src/utils/Doc.d.ts.map +1 -1
- package/dist/src/utils/ID.d.ts +2 -2
- package/dist/src/utils/ID.d.ts.map +1 -1
- package/dist/src/utils/IdMap.d.ts +22 -19
- package/dist/src/utils/IdMap.d.ts.map +1 -1
- package/dist/src/utils/IdSet.d.ts +6 -6
- package/dist/src/utils/IdSet.d.ts.map +1 -1
- package/dist/src/utils/RelativePosition.d.ts +8 -8
- package/dist/src/utils/RelativePosition.d.ts.map +1 -1
- package/dist/src/utils/Snapshot.d.ts +3 -3
- package/dist/src/utils/Snapshot.d.ts.map +1 -1
- package/dist/src/utils/Transaction.d.ts +9 -5
- package/dist/src/utils/Transaction.d.ts.map +1 -1
- package/dist/src/utils/UndoManager.d.ts +14 -12
- package/dist/src/utils/UndoManager.d.ts.map +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts +8 -4
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
- package/dist/src/utils/UpdateEncoder.d.ts +2 -0
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
- package/dist/src/utils/YEvent.d.ts +21 -42
- package/dist/src/utils/YEvent.d.ts.map +1 -1
- package/dist/src/utils/encoding.d.ts +3 -3
- package/dist/src/utils/encoding.d.ts.map +1 -1
- package/dist/src/utils/isParentOf.d.ts +1 -1
- package/dist/src/utils/isParentOf.d.ts.map +1 -1
- package/dist/src/utils/logging.d.ts +2 -2
- package/dist/src/utils/logging.d.ts.map +1 -1
- package/dist/src/utils/meta.d.ts +71 -0
- package/dist/src/utils/meta.d.ts.map +1 -0
- package/dist/src/utils/ts.d.ts +4 -0
- package/dist/src/utils/ts.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +11 -11
- package/dist/src/utils/updates.d.ts.map +1 -1
- package/dist/src/ytype.d.ts +498 -0
- package/dist/src/ytype.d.ts.map +1 -0
- package/dist/tests/IdMap.tests.d.ts.map +1 -1
- package/dist/tests/attribution.tests.d.ts +1 -0
- package/dist/tests/attribution.tests.d.ts.map +1 -1
- package/dist/tests/compatibility.tests.d.ts.map +1 -1
- package/dist/tests/doc.tests.d.ts.map +1 -1
- package/dist/tests/relativePositions.tests.d.ts +9 -9
- package/dist/tests/relativePositions.tests.d.ts.map +1 -1
- package/dist/tests/snapshot.tests.d.ts.map +1 -1
- package/dist/tests/testHelper.d.ts +28 -27
- package/dist/tests/testHelper.d.ts.map +1 -1
- package/dist/tests/undo-redo.tests.d.ts.map +1 -1
- package/dist/tests/updates.tests.d.ts +2 -1
- package/dist/tests/updates.tests.d.ts.map +1 -1
- package/dist/tests/y-array.tests.d.ts +0 -2
- package/dist/tests/y-array.tests.d.ts.map +1 -1
- package/dist/tests/y-map.tests.d.ts +0 -3
- package/dist/tests/y-map.tests.d.ts.map +1 -1
- package/dist/tests/y-text.tests.d.ts +1 -1
- package/dist/tests/y-text.tests.d.ts.map +1 -1
- package/dist/tests/y-xml.tests.d.ts +0 -1
- package/dist/tests/y-xml.tests.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/index.js +156 -0
- package/src/internals.js +35 -0
- package/src/structs/AbstractStruct.js +59 -0
- package/src/structs/ContentAny.js +115 -0
- package/src/structs/ContentBinary.js +93 -0
- package/src/structs/ContentDeleted.js +101 -0
- package/src/structs/ContentDoc.js +141 -0
- package/src/structs/ContentEmbed.js +98 -0
- package/src/structs/ContentFormat.js +105 -0
- package/src/structs/ContentJSON.js +119 -0
- package/src/structs/ContentString.js +113 -0
- package/src/structs/ContentType.js +152 -0
- package/src/structs/GC.js +80 -0
- package/src/structs/Item.js +841 -0
- package/src/structs/Skip.js +75 -0
- package/src/utils/AttributionManager.js +653 -0
- package/src/utils/Doc.js +266 -0
- package/src/utils/EventHandler.js +87 -0
- package/src/utils/ID.js +89 -0
- package/src/utils/IdMap.js +673 -0
- package/src/utils/IdSet.js +825 -0
- package/src/utils/RelativePosition.js +352 -0
- package/src/utils/Snapshot.js +220 -0
- package/src/utils/StructSet.js +137 -0
- package/src/utils/StructStore.js +289 -0
- package/src/utils/Transaction.js +671 -0
- package/src/utils/UndoManager.js +406 -0
- package/src/utils/UpdateDecoder.js +285 -0
- package/src/utils/UpdateEncoder.js +327 -0
- package/src/utils/YEvent.js +189 -0
- package/src/utils/delta-helpers.js +54 -0
- package/src/utils/encoding.js +623 -0
- package/src/utils/isParentOf.js +21 -0
- package/src/utils/logging.js +21 -0
- package/src/utils/meta.js +190 -0
- package/src/utils/ts.js +3 -0
- package/src/utils/updates.js +802 -0
- package/src/ytype.js +1962 -0
- package/dist/Skip-CE05BUF8.js +0 -11875
- package/dist/Skip-CE05BUF8.js.map +0 -1
- package/dist/index-C21sDQ5u.js +0 -163
- package/dist/index-C21sDQ5u.js.map +0 -1
- package/dist/internals.js +0 -25
- package/dist/internals.js.map +0 -1
- package/dist/src/types/AbstractType.d.ts +0 -239
- package/dist/src/types/AbstractType.d.ts.map +0 -1
- package/dist/src/types/YArray.d.ts +0 -128
- package/dist/src/types/YArray.d.ts.map +0 -1
- package/dist/src/types/YMap.d.ts +0 -112
- package/dist/src/types/YMap.d.ts.map +0 -1
- package/dist/src/types/YText.d.ts +0 -216
- package/dist/src/types/YText.d.ts.map +0 -1
- package/dist/src/types/YXmlElement.d.ts +0 -106
- package/dist/src/types/YXmlElement.d.ts.map +0 -1
- package/dist/src/types/YXmlFragment.d.ts +0 -143
- package/dist/src/types/YXmlFragment.d.ts.map +0 -1
- package/dist/src/types/YXmlHook.d.ts +0 -32
- package/dist/src/types/YXmlHook.d.ts.map +0 -1
- package/dist/src/types/YXmlText.d.ts +0 -34
- package/dist/src/types/YXmlText.d.ts.map +0 -1
- package/dist/src/utils/AbstractConnector.d.ts +0 -20
- package/dist/src/utils/AbstractConnector.d.ts.map +0 -1
- package/dist/src/utils/types.d.ts +0 -7
- package/dist/src/utils/types.d.ts.map +0 -1
- package/dist/testHelper.js +0 -617
- package/dist/testHelper.js.map +0 -1
- package/dist/yjs.js +0 -26
- package/dist/yjs.js.map +0 -1
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findIndexSS,
|
|
3
|
+
getState,
|
|
4
|
+
splitItem,
|
|
5
|
+
iterateStructs,
|
|
6
|
+
UpdateEncoderV2,
|
|
7
|
+
IdMap,
|
|
8
|
+
AttrRanges,
|
|
9
|
+
AttrRange,
|
|
10
|
+
Skip, AbstractStruct, IdSetDecoderV1, IdSetEncoderV1, IdSetDecoderV2, IdSetEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line
|
|
11
|
+
} from '../internals.js'
|
|
12
|
+
|
|
13
|
+
import * as array from 'lib0/array'
|
|
14
|
+
import * as math from 'lib0/math'
|
|
15
|
+
import * as encoding from 'lib0/encoding'
|
|
16
|
+
import * as decoding from 'lib0/decoding'
|
|
17
|
+
import * as traits from 'lib0/traits'
|
|
18
|
+
|
|
19
|
+
export class IdRange {
|
|
20
|
+
/**
|
|
21
|
+
* @param {number} clock
|
|
22
|
+
* @param {number} len
|
|
23
|
+
*/
|
|
24
|
+
constructor (clock, len) {
|
|
25
|
+
/**
|
|
26
|
+
* @type {number}
|
|
27
|
+
*/
|
|
28
|
+
this.clock = clock
|
|
29
|
+
/**
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
this.len = len
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {number} clock
|
|
37
|
+
* @param {number} len
|
|
38
|
+
*/
|
|
39
|
+
copyWith (clock, len) {
|
|
40
|
+
return new IdRange(clock, len)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Helper method making this compatible with IdMap.
|
|
45
|
+
*
|
|
46
|
+
* @return {Array<import('./IdMap.js').ContentAttribute<any>>}
|
|
47
|
+
*/
|
|
48
|
+
get attrs () {
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class MaybeIdRange {
|
|
54
|
+
/**
|
|
55
|
+
* @param {number} clock
|
|
56
|
+
* @param {number} len
|
|
57
|
+
* @param {boolean} exists
|
|
58
|
+
*/
|
|
59
|
+
constructor (clock, len, exists) {
|
|
60
|
+
/**
|
|
61
|
+
* @type {number}
|
|
62
|
+
*/
|
|
63
|
+
this.clock = clock
|
|
64
|
+
/**
|
|
65
|
+
* @type {number}
|
|
66
|
+
*/
|
|
67
|
+
this.len = len
|
|
68
|
+
/**
|
|
69
|
+
* @type {boolean}
|
|
70
|
+
*/
|
|
71
|
+
this.exists = exists
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {number} clock
|
|
77
|
+
* @param {number} len
|
|
78
|
+
* @param {boolean} exists
|
|
79
|
+
* @return {MaybeIdRange}
|
|
80
|
+
*/
|
|
81
|
+
export const createMaybeIdRange = (clock, len, exists) => new MaybeIdRange(clock, len, exists)
|
|
82
|
+
|
|
83
|
+
export class IdRanges {
|
|
84
|
+
/**
|
|
85
|
+
* @param {Array<IdRange>} ids
|
|
86
|
+
*/
|
|
87
|
+
constructor (ids) {
|
|
88
|
+
this.sorted = false
|
|
89
|
+
/**
|
|
90
|
+
* A typical use-case for IdSet is to append data. We heavily optimize this case by allowing the
|
|
91
|
+
* last item to be mutated ef it isn't used currently.
|
|
92
|
+
* This flag is true if the last item was exposed to the outside.
|
|
93
|
+
*/
|
|
94
|
+
this._lastIsUsed = false
|
|
95
|
+
/**
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
this._ids = ids
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
copy () {
|
|
102
|
+
return new IdRanges(this._ids.slice())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {number} clock
|
|
107
|
+
* @param {number} length
|
|
108
|
+
*/
|
|
109
|
+
add (clock, length) {
|
|
110
|
+
const last = this._ids[this._ids.length - 1]
|
|
111
|
+
if (last != null && last.clock + last.len === clock) {
|
|
112
|
+
if (this._lastIsUsed) {
|
|
113
|
+
this._ids[this._ids.length - 1] = new IdRange(last.clock, last.len + length)
|
|
114
|
+
this._lastIsUsed = false
|
|
115
|
+
} else {
|
|
116
|
+
this._ids[this._ids.length - 1].len += length
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
this.sorted = false
|
|
120
|
+
this._ids.push(new IdRange(clock, length))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Return the list of immutable id ranges, sorted and merged.
|
|
126
|
+
*/
|
|
127
|
+
getIds () {
|
|
128
|
+
const ids = this._ids
|
|
129
|
+
this._lastIsUsed = true
|
|
130
|
+
if (!this.sorted) {
|
|
131
|
+
this.sorted = true
|
|
132
|
+
ids.sort((a, b) => a.clock - b.clock)
|
|
133
|
+
// merge items without filtering or splicing the array
|
|
134
|
+
// i is the current pointer
|
|
135
|
+
// j refers to the current insert position for the pointed item
|
|
136
|
+
// try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
|
|
137
|
+
let i, j
|
|
138
|
+
for (i = 1, j = 1; i < ids.length; i++) {
|
|
139
|
+
const left = ids[j - 1]
|
|
140
|
+
const right = ids[i]
|
|
141
|
+
if (left.clock + left.len >= right.clock) {
|
|
142
|
+
const r = right.clock + right.len - left.clock
|
|
143
|
+
if (left.len < r) {
|
|
144
|
+
ids[j - 1] = new IdRange(left.clock, r)
|
|
145
|
+
}
|
|
146
|
+
} else if (left.len === 0) {
|
|
147
|
+
ids[j - 1] = right
|
|
148
|
+
} else {
|
|
149
|
+
if (j < i) {
|
|
150
|
+
ids[j] = right
|
|
151
|
+
}
|
|
152
|
+
j++
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
ids.length = ids[j - 1].len === 0 ? j - 1 : j
|
|
156
|
+
}
|
|
157
|
+
return ids
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @implements {traits.EqualityTrait}
|
|
163
|
+
*/
|
|
164
|
+
export class IdSet {
|
|
165
|
+
constructor () {
|
|
166
|
+
/**
|
|
167
|
+
* @type {Map<number,IdRanges>}
|
|
168
|
+
*/
|
|
169
|
+
this.clients = new Map()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
isEmpty () {
|
|
173
|
+
return this.clients.size === 0
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {(idrange:IdRange, client:number) => void} f
|
|
178
|
+
*/
|
|
179
|
+
forEach (f) {
|
|
180
|
+
this.clients.forEach((ranges, client) => {
|
|
181
|
+
ranges.getIds().forEach((range) => {
|
|
182
|
+
f(range, client)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {ID} id
|
|
189
|
+
* @return {boolean}
|
|
190
|
+
*/
|
|
191
|
+
hasId (id) {
|
|
192
|
+
return this.has(id.client, id.clock)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {number} client
|
|
197
|
+
* @param {number} clock
|
|
198
|
+
*/
|
|
199
|
+
has (client, clock) {
|
|
200
|
+
const dr = this.clients.get(client)
|
|
201
|
+
if (dr) {
|
|
202
|
+
return findIndexInIdRanges(dr.getIds(), clock) !== null
|
|
203
|
+
}
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Return slices of ids that exist in this idset.
|
|
209
|
+
*
|
|
210
|
+
* @param {number} client
|
|
211
|
+
* @param {number} clock
|
|
212
|
+
* @param {number} len
|
|
213
|
+
* @return {Array<MaybeIdRange>}
|
|
214
|
+
*/
|
|
215
|
+
slice (client, clock, len) {
|
|
216
|
+
const dr = this.clients.get(client)
|
|
217
|
+
/**
|
|
218
|
+
* @type {Array<MaybeIdRange>}
|
|
219
|
+
*/
|
|
220
|
+
const res = []
|
|
221
|
+
if (dr) {
|
|
222
|
+
/**
|
|
223
|
+
* @type {Array<IdRange>}
|
|
224
|
+
*/
|
|
225
|
+
const ranges = dr.getIds()
|
|
226
|
+
let index = findRangeStartInIdRanges(ranges, clock)
|
|
227
|
+
if (index !== null) {
|
|
228
|
+
let prev = null
|
|
229
|
+
while (index < ranges.length) {
|
|
230
|
+
let r = ranges[index]
|
|
231
|
+
if (r.clock < clock) {
|
|
232
|
+
r = new IdRange(clock, r.len - (clock - r.clock))
|
|
233
|
+
}
|
|
234
|
+
if (r.clock + r.len > clock + len) {
|
|
235
|
+
r = new IdRange(r.clock, clock + len - r.clock)
|
|
236
|
+
}
|
|
237
|
+
if (r.len <= 0) break
|
|
238
|
+
const prevEnd = prev != null ? prev.clock + prev.len : clock
|
|
239
|
+
if (prevEnd < r.clock) {
|
|
240
|
+
res.push(createMaybeIdRange(prevEnd, r.clock - prevEnd, false))
|
|
241
|
+
}
|
|
242
|
+
prev = r
|
|
243
|
+
res.push(createMaybeIdRange(r.clock, r.len, true))
|
|
244
|
+
index++
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (res.length > 0) {
|
|
249
|
+
const last = res[res.length - 1]
|
|
250
|
+
const end = last.clock + last.len
|
|
251
|
+
if (end < clock + len) {
|
|
252
|
+
res.push(createMaybeIdRange(end, clock + len - end, false))
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
res.push(createMaybeIdRange(clock, len, false))
|
|
256
|
+
}
|
|
257
|
+
return res
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @param {number} client
|
|
262
|
+
* @param {number} clock
|
|
263
|
+
* @param {number} len
|
|
264
|
+
*/
|
|
265
|
+
add (client, clock, len) {
|
|
266
|
+
addToIdSet(this, client, clock, len)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @param {number} client
|
|
271
|
+
* @param {number} clock
|
|
272
|
+
* @param {number} len
|
|
273
|
+
*/
|
|
274
|
+
delete (client, clock, len) {
|
|
275
|
+
_deleteRangeFromIdSet(this, client, clock, len)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {any} other
|
|
280
|
+
*/
|
|
281
|
+
[traits.EqualityTraitSymbol] (other) {
|
|
282
|
+
return equalIdSets(this, other)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @param {IdSet | IdMap<any>} set
|
|
288
|
+
* @param {number} client
|
|
289
|
+
* @param {number} clock
|
|
290
|
+
* @param {number} len
|
|
291
|
+
*/
|
|
292
|
+
export const _deleteRangeFromIdSet = (set, client, clock, len) => {
|
|
293
|
+
const dr = set.clients.get(client)
|
|
294
|
+
if (dr && len > 0) {
|
|
295
|
+
const ids = dr.getIds()
|
|
296
|
+
let index = findRangeStartInIdRanges(ids, clock)
|
|
297
|
+
if (index != null) {
|
|
298
|
+
for (let r = ids[index]; index < ids.length && r.clock < clock + len; r = ids[++index]) {
|
|
299
|
+
if (r.clock < clock) {
|
|
300
|
+
ids[index] = r.copyWith(r.clock, clock - r.clock)
|
|
301
|
+
if (clock + len < r.clock + r.len) {
|
|
302
|
+
ids.splice(index + 1, 0, r.copyWith(clock + len, r.clock + r.len - clock - len))
|
|
303
|
+
}
|
|
304
|
+
} else if (clock + len < r.clock + r.len) {
|
|
305
|
+
// need to retain end
|
|
306
|
+
ids[index] = r.copyWith(clock + len, r.clock + r.len - clock - len)
|
|
307
|
+
} else if (ids.length === 1) {
|
|
308
|
+
set.clients.delete(client)
|
|
309
|
+
return
|
|
310
|
+
} else {
|
|
311
|
+
ids.splice(index--, 1)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Iterate over all structs that are mentioned by the IdSet.
|
|
320
|
+
*
|
|
321
|
+
* @param {Transaction} transaction
|
|
322
|
+
* @param {IdSet} ds
|
|
323
|
+
* @param {function(GC|Item):void} f
|
|
324
|
+
*
|
|
325
|
+
* @function
|
|
326
|
+
*/
|
|
327
|
+
export const iterateStructsByIdSet = (transaction, ds, f) =>
|
|
328
|
+
ds.clients.forEach((idRanges, clientid) => {
|
|
329
|
+
const ranges = idRanges.getIds()
|
|
330
|
+
const structs = /** @type {Array<GC|Item>} */ (transaction.doc.store.clients.get(clientid))
|
|
331
|
+
if (structs != null) {
|
|
332
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
333
|
+
const del = ranges[i]
|
|
334
|
+
iterateStructs(transaction, structs, del.clock, del.len, f)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @param {Array<IdRange>} dis
|
|
341
|
+
* @param {number} clock
|
|
342
|
+
* @return {number|null}
|
|
343
|
+
*
|
|
344
|
+
* @private
|
|
345
|
+
* @function
|
|
346
|
+
*/
|
|
347
|
+
export const findIndexInIdRanges = (dis, clock) => {
|
|
348
|
+
let left = 0
|
|
349
|
+
let right = dis.length - 1
|
|
350
|
+
while (left <= right) {
|
|
351
|
+
const midindex = math.floor((left + right) / 2)
|
|
352
|
+
const mid = dis[midindex]
|
|
353
|
+
const midclock = mid.clock
|
|
354
|
+
if (midclock <= clock) {
|
|
355
|
+
if (clock < midclock + mid.len) {
|
|
356
|
+
return midindex
|
|
357
|
+
}
|
|
358
|
+
left = midindex + 1
|
|
359
|
+
} else {
|
|
360
|
+
right = midindex - 1
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return null
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Find the first range that contains clock or comes after clock.
|
|
368
|
+
*
|
|
369
|
+
* @param {Array<IdRange>} dis
|
|
370
|
+
* @param {number} clock
|
|
371
|
+
* @return {number|null}
|
|
372
|
+
*
|
|
373
|
+
* @private
|
|
374
|
+
* @function
|
|
375
|
+
*/
|
|
376
|
+
export const findRangeStartInIdRanges = (dis, clock) => {
|
|
377
|
+
let left = 0
|
|
378
|
+
let right = dis.length - 1
|
|
379
|
+
while (left <= right) {
|
|
380
|
+
const midindex = math.floor((left + right) / 2)
|
|
381
|
+
const mid = dis[midindex]
|
|
382
|
+
const midclock = mid.clock
|
|
383
|
+
if (midclock <= clock) {
|
|
384
|
+
if (clock < midclock + mid.len) {
|
|
385
|
+
return midindex
|
|
386
|
+
}
|
|
387
|
+
left = midindex + 1
|
|
388
|
+
} else {
|
|
389
|
+
right = midindex - 1
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return left < dis.length ? left : null
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @param {Array<IdSet>} idSets
|
|
397
|
+
* @return {IdSet} A fresh IdSet
|
|
398
|
+
*/
|
|
399
|
+
export const mergeIdSets = idSets => {
|
|
400
|
+
const merged = new IdSet()
|
|
401
|
+
for (let dssI = 0; dssI < idSets.length; dssI++) {
|
|
402
|
+
idSets[dssI].clients.forEach((rangesLeft, client) => {
|
|
403
|
+
if (!merged.clients.has(client)) {
|
|
404
|
+
// Write all missing keys from current ds and all following.
|
|
405
|
+
// If merged already contains `client` current ds has already been added.
|
|
406
|
+
const ids = rangesLeft.getIds().slice()
|
|
407
|
+
for (let i = dssI + 1; i < idSets.length; i++) {
|
|
408
|
+
const nextIds = idSets[i].clients.get(client)
|
|
409
|
+
if (nextIds) {
|
|
410
|
+
array.appendTo(ids, nextIds.getIds())
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
merged.clients.set(client, new IdRanges(ids))
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
return merged
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @template {IdSet | IdMap<any>} S
|
|
422
|
+
* @param {S} dest
|
|
423
|
+
* @param {S} src
|
|
424
|
+
*/
|
|
425
|
+
export const _insertIntoIdSet = (dest, src) => {
|
|
426
|
+
src.clients.forEach((srcRanges, client) => {
|
|
427
|
+
const targetRanges = dest.clients.get(client)
|
|
428
|
+
if (targetRanges) {
|
|
429
|
+
array.appendTo(targetRanges.getIds(), srcRanges.getIds())
|
|
430
|
+
targetRanges.sorted = false
|
|
431
|
+
} else {
|
|
432
|
+
const res = srcRanges.copy()
|
|
433
|
+
res.sorted = true
|
|
434
|
+
dest.clients.set(client, /** @type {any} */ (res))
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* @param {IdSet} dest
|
|
441
|
+
* @param {IdSet} src
|
|
442
|
+
*/
|
|
443
|
+
export const insertIntoIdSet = _insertIntoIdSet
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @todo rename to excludeIdSet | excludeIdMap
|
|
447
|
+
*
|
|
448
|
+
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not
|
|
449
|
+
* in `exclude`.
|
|
450
|
+
*
|
|
451
|
+
* @template {IdSet | IdMap<any>} Set
|
|
452
|
+
* @param {Set} set
|
|
453
|
+
* @param {IdSet | IdMap<any>} exclude
|
|
454
|
+
* @return {Set}
|
|
455
|
+
*/
|
|
456
|
+
export const _diffSet = (set, exclude) => {
|
|
457
|
+
/**
|
|
458
|
+
* @type {Set}
|
|
459
|
+
*/
|
|
460
|
+
const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new IdMap())
|
|
461
|
+
const Ranges = set instanceof IdSet ? IdRanges : AttrRanges
|
|
462
|
+
set.clients.forEach((_setRanges, client) => {
|
|
463
|
+
/**
|
|
464
|
+
* @type {Array<IdRange>}
|
|
465
|
+
*/
|
|
466
|
+
let resRanges = []
|
|
467
|
+
const _excludedRanges = exclude.clients.get(client)
|
|
468
|
+
const setRanges = _setRanges.getIds()
|
|
469
|
+
if (_excludedRanges == null) {
|
|
470
|
+
resRanges = setRanges.slice()
|
|
471
|
+
} else {
|
|
472
|
+
const excludedRanges = _excludedRanges.getIds()
|
|
473
|
+
let i = 0; let j = 0
|
|
474
|
+
let currRange = setRanges[0]
|
|
475
|
+
while (i < setRanges.length && j < excludedRanges.length) {
|
|
476
|
+
const e = excludedRanges[j]
|
|
477
|
+
if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item
|
|
478
|
+
if (currRange.len > 0) resRanges.push(currRange)
|
|
479
|
+
currRange = setRanges[++i]
|
|
480
|
+
} else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item
|
|
481
|
+
j++
|
|
482
|
+
} else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide)
|
|
483
|
+
const newClock = e.clock + e.len
|
|
484
|
+
const newLen = currRange.clock + currRange.len - newClock
|
|
485
|
+
if (newLen > 0) {
|
|
486
|
+
currRange = currRange.copyWith(newClock, newLen)
|
|
487
|
+
j++
|
|
488
|
+
} else {
|
|
489
|
+
// this item is completely overwritten. len=0. We can jump to the next range
|
|
490
|
+
currRange = setRanges[++i]
|
|
491
|
+
}
|
|
492
|
+
} else { // currRange.clock < e.clock -- range laps into exclude => adjust len
|
|
493
|
+
// beginning can't be empty, add it to the result
|
|
494
|
+
const nextLen = e.clock - currRange.clock
|
|
495
|
+
resRanges.push(currRange.copyWith(currRange.clock, nextLen))
|
|
496
|
+
// retain the remaining length after exclude in currRange
|
|
497
|
+
currRange = currRange.copyWith(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0))
|
|
498
|
+
if (currRange.len === 0) currRange = setRanges[++i]
|
|
499
|
+
else j++
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (currRange != null) {
|
|
503
|
+
resRanges.push(currRange)
|
|
504
|
+
}
|
|
505
|
+
i++
|
|
506
|
+
while (i < setRanges.length) {
|
|
507
|
+
resRanges.push(setRanges[i++])
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// @ts-ignore
|
|
511
|
+
if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
|
|
512
|
+
})
|
|
513
|
+
return res
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Remove all ranges from `exclude` from `idSet`. The result is a fresh IdSet containing all ranges from `idSet` that are not
|
|
518
|
+
* in `exclude`.
|
|
519
|
+
*
|
|
520
|
+
* @type {(idSet: IdSet, exclude: IdSet|IdMap<any>) => IdSet}
|
|
521
|
+
*/
|
|
522
|
+
export const diffIdSet = _diffSet
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* @template {IdSet | IdMap<any>} SetA
|
|
526
|
+
* @template {IdSet | IdMap<any>} SetB
|
|
527
|
+
* @param {SetA} setA
|
|
528
|
+
* @param {SetB} setB
|
|
529
|
+
* @return {SetA extends IdMap<infer A> ? (SetB extends IdMap<infer B> ? IdMap<A | B> : IdMap<A>) : IdSet}
|
|
530
|
+
*/
|
|
531
|
+
export const _intersectSets = (setA, setB) => {
|
|
532
|
+
/**
|
|
533
|
+
* @type {IdMap<any> | IdSet}
|
|
534
|
+
*/
|
|
535
|
+
const res = /** @type {any } */ (setA instanceof IdSet ? new IdSet() : new IdMap())
|
|
536
|
+
const Ranges = setA instanceof IdSet ? IdRanges : AttrRanges
|
|
537
|
+
setA.clients.forEach((_aRanges, client) => {
|
|
538
|
+
/**
|
|
539
|
+
* @type {Array<IdRange>}
|
|
540
|
+
*/
|
|
541
|
+
const resRanges = []
|
|
542
|
+
const _bRanges = setB.clients.get(client)
|
|
543
|
+
const aRanges = _aRanges.getIds()
|
|
544
|
+
if (_bRanges != null) {
|
|
545
|
+
const bRanges = _bRanges.getIds()
|
|
546
|
+
for (let a = 0, b = 0; a < aRanges.length && b < bRanges.length;) {
|
|
547
|
+
const aRange = aRanges[a]
|
|
548
|
+
const bRange = bRanges[b]
|
|
549
|
+
// construct overlap
|
|
550
|
+
const clock = math.max(aRange.clock, bRange.clock)
|
|
551
|
+
const len = math.min(aRange.len - (clock - aRange.clock), bRange.len - (clock - bRange.clock))
|
|
552
|
+
if (len > 0) {
|
|
553
|
+
resRanges.push(aRange instanceof AttrRange
|
|
554
|
+
? new AttrRange(clock, len, /** @type {Array<any>} */ (aRange.attrs).concat(bRange.attrs))
|
|
555
|
+
: new IdRange(clock, len)
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
if (aRange.clock + aRange.len < bRange.clock + bRange.len) {
|
|
559
|
+
a++
|
|
560
|
+
} else {
|
|
561
|
+
b++
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// @ts-ignore
|
|
566
|
+
if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
|
|
567
|
+
})
|
|
568
|
+
return /** @type {any} */ (res)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export const intersectSets = _intersectSets
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* @param {IdSet} idSet
|
|
575
|
+
* @param {number} client
|
|
576
|
+
* @param {number} clock
|
|
577
|
+
* @param {number} length
|
|
578
|
+
*
|
|
579
|
+
* @private
|
|
580
|
+
* @function
|
|
581
|
+
*/
|
|
582
|
+
export const addToIdSet = (idSet, client, clock, length) => {
|
|
583
|
+
if (length === 0) return
|
|
584
|
+
const idRanges = idSet.clients.get(client)
|
|
585
|
+
if (idRanges) {
|
|
586
|
+
idRanges.add(clock, length)
|
|
587
|
+
} else {
|
|
588
|
+
idSet.clients.set(client, new IdRanges([new IdRange(clock, length)]))
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* @param {IdSet} idSet
|
|
594
|
+
* @param {AbstractStruct} struct
|
|
595
|
+
*
|
|
596
|
+
* @private
|
|
597
|
+
* @function
|
|
598
|
+
*/
|
|
599
|
+
export const addStructToIdSet = (idSet, struct) => addToIdSet(idSet, struct.id.client, struct.id.clock, struct.length)
|
|
600
|
+
|
|
601
|
+
export const createIdSet = () => new IdSet()
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* @param {StructStore} ss
|
|
605
|
+
* @return {IdSet}
|
|
606
|
+
*
|
|
607
|
+
* @private
|
|
608
|
+
* @function
|
|
609
|
+
*/
|
|
610
|
+
export const createDeleteSetFromStructStore = ss => {
|
|
611
|
+
const ds = createIdSet()
|
|
612
|
+
ss.clients.forEach((structs, client) => {
|
|
613
|
+
/**
|
|
614
|
+
* @type {Array<IdRange>}
|
|
615
|
+
*/
|
|
616
|
+
const dsitems = []
|
|
617
|
+
for (let i = 0; i < structs.length; i++) {
|
|
618
|
+
const struct = structs[i]
|
|
619
|
+
if (struct.deleted) {
|
|
620
|
+
const clock = struct.id.clock
|
|
621
|
+
let len = struct.length
|
|
622
|
+
if (i + 1 < structs.length) {
|
|
623
|
+
for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) {
|
|
624
|
+
len += next.length
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
dsitems.push(new IdRange(clock, len))
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (dsitems.length > 0) {
|
|
631
|
+
ds.clients.set(client, new IdRanges(dsitems))
|
|
632
|
+
}
|
|
633
|
+
})
|
|
634
|
+
return ds
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* @param {Array<GC | Item>} structs
|
|
639
|
+
* @param {boolean} filterDeleted
|
|
640
|
+
*
|
|
641
|
+
*/
|
|
642
|
+
export const _createInsertSliceFromStructs = (structs, filterDeleted) => {
|
|
643
|
+
/**
|
|
644
|
+
* @type {Array<IdRange>}
|
|
645
|
+
*/
|
|
646
|
+
const iditems = []
|
|
647
|
+
for (let i = 0; i < structs.length; i++) {
|
|
648
|
+
const struct = structs[i]
|
|
649
|
+
if (!(filterDeleted && struct.deleted)) {
|
|
650
|
+
const clock = struct.id.clock
|
|
651
|
+
let len = struct.length
|
|
652
|
+
if (i + 1 < structs.length) {
|
|
653
|
+
// eslint-disable-next-line
|
|
654
|
+
for (let next = structs[i + 1]; i + 1 < structs.length && !(filterDeleted && next.deleted); next = structs[++i + 1]) {
|
|
655
|
+
len += next.length
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
iditems.push(new IdRange(clock, len))
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return iditems
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @param {import('../internals.js').StructStore} ss
|
|
666
|
+
* @param {boolean} filterDeleted
|
|
667
|
+
*/
|
|
668
|
+
export const createInsertSetFromStructStore = (ss, filterDeleted) => {
|
|
669
|
+
const idset = createIdSet()
|
|
670
|
+
ss.clients.forEach((structs, client) => {
|
|
671
|
+
const iditems = _createInsertSliceFromStructs(structs, filterDeleted)
|
|
672
|
+
if (iditems.length !== 0) {
|
|
673
|
+
idset.clients.set(client, new IdRanges(iditems))
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
return idset
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
|
681
|
+
* @param {IdSet} idSet
|
|
682
|
+
*
|
|
683
|
+
* @private
|
|
684
|
+
* @function
|
|
685
|
+
*/
|
|
686
|
+
export const writeIdSet = (encoder, idSet) => {
|
|
687
|
+
encoding.writeVarUint(encoder.restEncoder, idSet.clients.size)
|
|
688
|
+
// Ensure that the delete set is written in a deterministic order
|
|
689
|
+
array.from(idSet.clients.entries())
|
|
690
|
+
.sort((a, b) => b[0] - a[0])
|
|
691
|
+
.forEach(([client, _idRanges]) => {
|
|
692
|
+
const idRanges = _idRanges.getIds()
|
|
693
|
+
encoder.resetIdSetCurVal()
|
|
694
|
+
encoding.writeVarUint(encoder.restEncoder, client)
|
|
695
|
+
const len = idRanges.length
|
|
696
|
+
encoding.writeVarUint(encoder.restEncoder, len)
|
|
697
|
+
for (let i = 0; i < len; i++) {
|
|
698
|
+
const item = idRanges[i]
|
|
699
|
+
encoder.writeIdSetClock(item.clock)
|
|
700
|
+
encoder.writeIdSetLen(item.len)
|
|
701
|
+
}
|
|
702
|
+
})
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
|
|
707
|
+
* @return {IdSet}
|
|
708
|
+
*
|
|
709
|
+
* @private
|
|
710
|
+
* @function
|
|
711
|
+
*/
|
|
712
|
+
export const readIdSet = decoder => {
|
|
713
|
+
const ds = new IdSet()
|
|
714
|
+
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
715
|
+
for (let i = 0; i < numClients; i++) {
|
|
716
|
+
decoder.resetDsCurVal()
|
|
717
|
+
const client = decoding.readVarUint(decoder.restDecoder)
|
|
718
|
+
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
719
|
+
if (numberOfDeletes > 0) {
|
|
720
|
+
/**
|
|
721
|
+
* @type {Array<IdRange>}
|
|
722
|
+
*/
|
|
723
|
+
const dsRanges = []
|
|
724
|
+
for (let i = 0; i < numberOfDeletes; i++) {
|
|
725
|
+
dsRanges.push(new IdRange(decoder.readDsClock(), decoder.readDsLen()))
|
|
726
|
+
}
|
|
727
|
+
ds.clients.set(client, new IdRanges(dsRanges))
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return ds
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array()..
|
|
735
|
+
*/
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
|
|
739
|
+
* @param {Transaction} transaction
|
|
740
|
+
* @param {StructStore} store
|
|
741
|
+
* @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.
|
|
742
|
+
*
|
|
743
|
+
* @private
|
|
744
|
+
* @function
|
|
745
|
+
*/
|
|
746
|
+
export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
|
747
|
+
const unappliedDS = new IdSet()
|
|
748
|
+
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
749
|
+
for (let i = 0; i < numClients; i++) {
|
|
750
|
+
decoder.resetDsCurVal()
|
|
751
|
+
const client = decoding.readVarUint(decoder.restDecoder)
|
|
752
|
+
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
753
|
+
const structs = store.clients.get(client) || []
|
|
754
|
+
const state = getState(store, client)
|
|
755
|
+
for (let i = 0; i < numberOfDeletes; i++) {
|
|
756
|
+
const clock = decoder.readDsClock()
|
|
757
|
+
const clockEnd = clock + decoder.readDsLen()
|
|
758
|
+
if (clock < state) {
|
|
759
|
+
if (state < clockEnd) {
|
|
760
|
+
addToIdSet(unappliedDS, client, state, clockEnd - state)
|
|
761
|
+
}
|
|
762
|
+
let index = findIndexSS(structs, clock)
|
|
763
|
+
/**
|
|
764
|
+
* We can ignore the case of GC and Delete structs, because we are going to skip them
|
|
765
|
+
* @type {Item | GC | Skip}
|
|
766
|
+
*/
|
|
767
|
+
let struct = structs[index]
|
|
768
|
+
// split the first item if necessary
|
|
769
|
+
if (!struct.deleted && struct.id.clock < clock && struct instanceof Item) {
|
|
770
|
+
// increment index, we now want to use the next struct
|
|
771
|
+
structs.splice(++index, 0, splitItem(transaction, struct, clock - struct.id.clock))
|
|
772
|
+
}
|
|
773
|
+
while (index < structs.length) {
|
|
774
|
+
// @ts-ignore
|
|
775
|
+
struct = structs[index++]
|
|
776
|
+
if (struct.id.clock < clockEnd) {
|
|
777
|
+
if (!struct.deleted) {
|
|
778
|
+
if (struct instanceof Item) {
|
|
779
|
+
if (clockEnd < struct.id.clock + struct.length) {
|
|
780
|
+
structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock))
|
|
781
|
+
}
|
|
782
|
+
struct.delete(transaction)
|
|
783
|
+
} else { // is a Skip - add range to unappliedDS
|
|
784
|
+
const c = math.max(struct.id.clock, clock)
|
|
785
|
+
unappliedDS.add(client, c, math.min(struct.length, clockEnd - c))
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
break
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
addToIdSet(unappliedDS, client, clock, clockEnd - clock)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (unappliedDS.clients.size > 0) {
|
|
798
|
+
const ds = new UpdateEncoderV2()
|
|
799
|
+
encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs
|
|
800
|
+
writeIdSet(ds, unappliedDS)
|
|
801
|
+
return ds.toUint8Array()
|
|
802
|
+
}
|
|
803
|
+
return null
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* @param {IdSet} ds1
|
|
808
|
+
* @param {IdSet} ds2
|
|
809
|
+
*/
|
|
810
|
+
export const equalIdSets = (ds1, ds2) => {
|
|
811
|
+
if (ds1.clients.size !== ds2.clients.size) return false
|
|
812
|
+
for (const [client, _deleteItems1] of ds1.clients.entries()) {
|
|
813
|
+
const deleteItems1 = _deleteItems1.getIds()
|
|
814
|
+
const deleteItems2 = ds2.clients.get(client)?.getIds()
|
|
815
|
+
if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false
|
|
816
|
+
for (let i = 0; i < deleteItems1.length; i++) {
|
|
817
|
+
const di1 = deleteItems1[i]
|
|
818
|
+
const di2 = deleteItems2[i]
|
|
819
|
+
if (di1.clock !== di2.clock || di1.len !== di2.len) {
|
|
820
|
+
return false
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return true
|
|
825
|
+
}
|