@y/y 14.0.0-16 → 14.0.0-17
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/dist/{index-DyTeTfmj.js → index-BV-j5wdP.js} +2 -2
- package/dist/{index-R7GxO-36.js.map → index-BV-j5wdP.js.map} +1 -1
- package/dist/{internals.mjs → internals.js} +1 -1
- package/dist/internals.js.map +1 -0
- package/dist/{testHelper.mjs → testHelper.js} +2 -2
- package/dist/testHelper.js.map +1 -0
- package/dist/{yjs.mjs → yjs.js} +2 -2
- package/dist/yjs.js.map +1 -0
- package/package.json +8 -17
- package/dist/Skip-j0kX7pdq.js +0 -12173
- package/dist/Skip-j0kX7pdq.js.map +0 -1
- package/dist/index-DyTeTfmj.js.map +0 -1
- package/dist/index-R7GxO-36.js +0 -165
- package/dist/internals.cjs +0 -286
- package/dist/internals.cjs.map +0 -1
- package/dist/internals.mjs.map +0 -1
- package/dist/testHelper.cjs +0 -780
- package/dist/testHelper.cjs.map +0 -1
- package/dist/testHelper.mjs.map +0 -1
- package/dist/yjs.cjs +0 -151
- package/dist/yjs.cjs.map +0 -1
- package/dist/yjs.mjs.map +0 -1
- package/src/index.js +0 -153
- package/src/internals.js +0 -44
- package/src/structs/AbstractStruct.js +0 -59
- package/src/structs/ContentAny.js +0 -115
- package/src/structs/ContentBinary.js +0 -93
- package/src/structs/ContentDeleted.js +0 -101
- package/src/structs/ContentDoc.js +0 -141
- package/src/structs/ContentEmbed.js +0 -98
- package/src/structs/ContentFormat.js +0 -105
- package/src/structs/ContentJSON.js +0 -119
- package/src/structs/ContentString.js +0 -113
- package/src/structs/ContentType.js +0 -176
- package/src/structs/GC.js +0 -80
- package/src/structs/Item.js +0 -845
- package/src/structs/Skip.js +0 -75
- package/src/types/AbstractType.js +0 -1434
- package/src/types/YArray.js +0 -270
- package/src/types/YMap.js +0 -244
- package/src/types/YText.js +0 -934
- package/src/types/YXmlElement.js +0 -227
- package/src/types/YXmlFragment.js +0 -266
- package/src/types/YXmlHook.js +0 -68
- package/src/types/YXmlText.js +0 -66
- package/src/utils/AbstractConnector.js +0 -25
- package/src/utils/AttributionManager.js +0 -619
- package/src/utils/Doc.js +0 -372
- package/src/utils/EventHandler.js +0 -87
- package/src/utils/ID.js +0 -89
- package/src/utils/IdMap.js +0 -629
- package/src/utils/IdSet.js +0 -823
- package/src/utils/RelativePosition.js +0 -352
- package/src/utils/Snapshot.js +0 -220
- package/src/utils/StructSet.js +0 -137
- package/src/utils/StructStore.js +0 -289
- package/src/utils/Transaction.js +0 -489
- package/src/utils/UndoManager.js +0 -391
- package/src/utils/UpdateDecoder.js +0 -281
- package/src/utils/UpdateEncoder.js +0 -320
- package/src/utils/YEvent.js +0 -216
- package/src/utils/delta-helpers.js +0 -54
- package/src/utils/encoding.js +0 -623
- package/src/utils/isParentOf.js +0 -21
- package/src/utils/logging.js +0 -21
- package/src/utils/types.js +0 -28
- package/src/utils/updates.js +0 -715
- package/tests/testHelper.js +0 -600
package/src/utils/encoding.js
DELETED
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module encoding
|
|
3
|
-
*/
|
|
4
|
-
/*
|
|
5
|
-
* We use the first five bits in the info flag for determining the type of the struct.
|
|
6
|
-
*
|
|
7
|
-
* 0: GC
|
|
8
|
-
* 1: Item with Deleted content
|
|
9
|
-
* 2: Item with JSON content
|
|
10
|
-
* 3: Item with Binary content
|
|
11
|
-
* 4: Item with String content
|
|
12
|
-
* 5: Item with Embed content (for richtext content)
|
|
13
|
-
* 6: Item with Format content (a formatting marker for richtext content)
|
|
14
|
-
* 7: Item with Type
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
findIndexSS,
|
|
19
|
-
getState,
|
|
20
|
-
getStateVector,
|
|
21
|
-
readAndApplyDeleteSet,
|
|
22
|
-
writeIdSet,
|
|
23
|
-
transact,
|
|
24
|
-
UpdateDecoderV1,
|
|
25
|
-
UpdateDecoderV2,
|
|
26
|
-
UpdateEncoderV1,
|
|
27
|
-
UpdateEncoderV2,
|
|
28
|
-
IdSetEncoderV2,
|
|
29
|
-
DSDecoderV1,
|
|
30
|
-
IdSetEncoderV1,
|
|
31
|
-
mergeUpdates,
|
|
32
|
-
mergeUpdatesV2,
|
|
33
|
-
Skip,
|
|
34
|
-
diffUpdateV2,
|
|
35
|
-
convertUpdateFormatV2ToV1,
|
|
36
|
-
readStructSet,
|
|
37
|
-
removeRangesFromStructSet,
|
|
38
|
-
createIdSet,
|
|
39
|
-
StructSet, IdSet, DSDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line
|
|
40
|
-
createID,
|
|
41
|
-
IdRange
|
|
42
|
-
} from '../internals.js'
|
|
43
|
-
|
|
44
|
-
import * as encoding from 'lib0/encoding'
|
|
45
|
-
import * as decoding from 'lib0/decoding'
|
|
46
|
-
import * as map from 'lib0/map'
|
|
47
|
-
import * as math from 'lib0/math'
|
|
48
|
-
import * as array from 'lib0/array'
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
|
52
|
-
* @param {Array<GC|Item>} structs All structs by `client`
|
|
53
|
-
* @param {number} client
|
|
54
|
-
* @param {Array<IdRange>} idranges
|
|
55
|
-
*
|
|
56
|
-
* @function
|
|
57
|
-
*/
|
|
58
|
-
const writeStructs = (encoder, structs, client, idranges) => {
|
|
59
|
-
let structsToWrite = 0 // this accounts for the skips
|
|
60
|
-
/**
|
|
61
|
-
* @type {Array<{ start: number, end: number, startClock: number, endClock: number }>}
|
|
62
|
-
*/
|
|
63
|
-
const indexRanges = []
|
|
64
|
-
const firstPossibleClock = structs[0].id.clock
|
|
65
|
-
const lastStruct = array.last(structs)
|
|
66
|
-
const lastPossibleClock = lastStruct.id.clock + lastStruct.length
|
|
67
|
-
idranges.forEach(idrange => {
|
|
68
|
-
const startClock = math.max(idrange.clock, firstPossibleClock)
|
|
69
|
-
const endClock = math.min(idrange.clock + idrange.len, lastPossibleClock)
|
|
70
|
-
if (startClock >= endClock) return // structs for this range do not exist
|
|
71
|
-
// inclusive start
|
|
72
|
-
const start = findIndexSS(structs, startClock)
|
|
73
|
-
// exclusive end
|
|
74
|
-
const end = findIndexSS(structs, endClock - 1) + 1
|
|
75
|
-
structsToWrite += end - start
|
|
76
|
-
indexRanges.push({
|
|
77
|
-
start,
|
|
78
|
-
end,
|
|
79
|
-
startClock,
|
|
80
|
-
endClock
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
structsToWrite += idranges.length - 1
|
|
84
|
-
// start writing with this clock. this is updated to the next clock that we expect to write
|
|
85
|
-
let clock = indexRanges[0].startClock
|
|
86
|
-
// write # encoded structs
|
|
87
|
-
encoding.writeVarUint(encoder.restEncoder, structsToWrite)
|
|
88
|
-
encoder.writeClient(client)
|
|
89
|
-
// write clock
|
|
90
|
-
encoding.writeVarUint(encoder.restEncoder, clock)
|
|
91
|
-
indexRanges.forEach(indexRange => {
|
|
92
|
-
const skipLen = indexRange.startClock - clock
|
|
93
|
-
if (skipLen > 0) {
|
|
94
|
-
new Skip(createID(client, clock), skipLen).write(encoder, 0)
|
|
95
|
-
clock += skipLen
|
|
96
|
-
}
|
|
97
|
-
for (let i = indexRange.start; i < indexRange.end; i++) {
|
|
98
|
-
const struct = structs[i]
|
|
99
|
-
const structEnd = struct.id.clock + struct.length
|
|
100
|
-
const offsetEnd = math.max(structEnd - indexRange.endClock, 0)
|
|
101
|
-
struct.write(encoder, clock - struct.id.clock, offsetEnd)
|
|
102
|
-
clock = structEnd - offsetEnd
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
|
109
|
-
* @param {StructStore} store
|
|
110
|
-
* @param {Map<number,number>} _sm
|
|
111
|
-
*
|
|
112
|
-
* @private
|
|
113
|
-
* @function
|
|
114
|
-
*/
|
|
115
|
-
export const writeClientsStructs = (encoder, store, _sm) => {
|
|
116
|
-
// we filter all valid _sm entries into sm
|
|
117
|
-
const sm = new Map()
|
|
118
|
-
_sm.forEach((clock, client) => {
|
|
119
|
-
// only write if new structs are available
|
|
120
|
-
if (getState(store, client) > clock) {
|
|
121
|
-
sm.set(client, clock)
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
getStateVector(store).forEach((_clock, client) => {
|
|
125
|
-
if (!_sm.has(client)) {
|
|
126
|
-
sm.set(client, 0)
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
// write # states that were updated
|
|
130
|
-
encoding.writeVarUint(encoder.restEncoder, sm.size)
|
|
131
|
-
// Write items with higher client ids first
|
|
132
|
-
// This heavily improves the conflict algorithm.
|
|
133
|
-
array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
|
|
134
|
-
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
|
|
135
|
-
const lastStruct = structs[structs.length - 1]
|
|
136
|
-
writeStructs(encoder, structs, client, [new IdRange(clock, lastStruct.id.clock + lastStruct.length - clock)])
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
|
142
|
-
* @param {StructStore} store
|
|
143
|
-
* @param {IdSet} idset
|
|
144
|
-
*
|
|
145
|
-
* @todo at the moment this writes the full deleteset range
|
|
146
|
-
*
|
|
147
|
-
* @private
|
|
148
|
-
* @function
|
|
149
|
-
*/
|
|
150
|
-
export const writeStructsFromIdSet = (encoder, store, idset) => {
|
|
151
|
-
// write # states that were updated
|
|
152
|
-
encoding.writeVarUint(encoder.restEncoder, idset.clients.size)
|
|
153
|
-
// Write items with higher client ids first
|
|
154
|
-
// This heavily improves the conflict algorithm.
|
|
155
|
-
array.from(idset.clients.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, ids]) => {
|
|
156
|
-
const idRanges = ids.getIds()
|
|
157
|
-
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
|
|
158
|
-
writeStructs(encoder, structs, client, idRanges)
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Resume computing structs generated by struct readers.
|
|
164
|
-
*
|
|
165
|
-
* While there is something to do, we integrate structs in this order
|
|
166
|
-
* 1. top element on stack, if stack is not empty
|
|
167
|
-
* 2. next element from current struct reader (if empty, use next struct reader)
|
|
168
|
-
*
|
|
169
|
-
* If struct causally depends on another struct (ref.missing), we put next reader of
|
|
170
|
-
* `ref.id.client` on top of stack.
|
|
171
|
-
*
|
|
172
|
-
* At some point we find a struct that has no causal dependencies,
|
|
173
|
-
* then we start emptying the stack.
|
|
174
|
-
*
|
|
175
|
-
* It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2)
|
|
176
|
-
* depends on struct3 (from client1). Therefore the max stack size is equal to `structReaders.length`.
|
|
177
|
-
*
|
|
178
|
-
* This method is implemented in a way so that we can resume computation if this update
|
|
179
|
-
* causally depends on another update.
|
|
180
|
-
*
|
|
181
|
-
* @param {Transaction} transaction
|
|
182
|
-
* @param {StructStore} store
|
|
183
|
-
* @param {StructSet} clientsStructRefs
|
|
184
|
-
* @return { null | { update: Uint8Array<ArrayBuffer>, missing: Map<number,number> } }
|
|
185
|
-
*
|
|
186
|
-
* @private
|
|
187
|
-
* @function
|
|
188
|
-
*/
|
|
189
|
-
const integrateStructs = (transaction, store, clientsStructRefs) => {
|
|
190
|
-
/**
|
|
191
|
-
* @type {Array<Item | GC>}
|
|
192
|
-
*/
|
|
193
|
-
const stack = []
|
|
194
|
-
// sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user.
|
|
195
|
-
let clientsStructRefsIds = array.from(clientsStructRefs.clients.keys()).sort((a, b) => a - b)
|
|
196
|
-
if (clientsStructRefsIds.length === 0) {
|
|
197
|
-
return null
|
|
198
|
-
}
|
|
199
|
-
const getNextStructTarget = () => {
|
|
200
|
-
if (clientsStructRefsIds.length === 0) {
|
|
201
|
-
return null
|
|
202
|
-
}
|
|
203
|
-
let nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.clients.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
|
|
204
|
-
while (nextStructsTarget.refs.length === nextStructsTarget.i) {
|
|
205
|
-
clientsStructRefsIds.pop()
|
|
206
|
-
if (clientsStructRefsIds.length > 0) {
|
|
207
|
-
nextStructsTarget = /** @type {{i:number,refs:Array<GC|Item>}} */ (clientsStructRefs.clients.get(clientsStructRefsIds[clientsStructRefsIds.length - 1]))
|
|
208
|
-
} else {
|
|
209
|
-
return null
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return nextStructsTarget
|
|
213
|
-
}
|
|
214
|
-
let curStructsTarget = getNextStructTarget()
|
|
215
|
-
if (curStructsTarget === null) {
|
|
216
|
-
return null
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* @type {StructStore}
|
|
221
|
-
*/
|
|
222
|
-
const restStructs = new StructStore()
|
|
223
|
-
const missingSV = new Map()
|
|
224
|
-
/**
|
|
225
|
-
* @param {number} client
|
|
226
|
-
* @param {number} clock
|
|
227
|
-
*/
|
|
228
|
-
const updateMissingSv = (client, clock) => {
|
|
229
|
-
const mclock = missingSV.get(client)
|
|
230
|
-
if (mclock == null || mclock > clock) {
|
|
231
|
-
missingSV.set(client, clock)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* @type {GC|Item}
|
|
236
|
-
*/
|
|
237
|
-
let stackHead = /** @type {any} */ (curStructsTarget).refs[/** @type {any} */ (curStructsTarget).i++]
|
|
238
|
-
// caching the state because it is used very often
|
|
239
|
-
const state = new Map()
|
|
240
|
-
|
|
241
|
-
// // caching the state because it is used very often
|
|
242
|
-
// const currentInsertSet = createIdSet()
|
|
243
|
-
// clientsStructRefsIds.forEach(clientId => {
|
|
244
|
-
// currentInsertSet.clients.set(clientid, new IdRanges(_createInsertSliceFromStructs(store.clients.get(clientId) ?? [], false)))
|
|
245
|
-
// })
|
|
246
|
-
|
|
247
|
-
const addStackToRestSS = () => {
|
|
248
|
-
for (const item of stack) {
|
|
249
|
-
const client = item.id.client
|
|
250
|
-
const inapplicableItems = clientsStructRefs.clients.get(client)
|
|
251
|
-
if (inapplicableItems) {
|
|
252
|
-
// decrement because we weren't able to apply previous operation
|
|
253
|
-
inapplicableItems.i--
|
|
254
|
-
restStructs.clients.set(client, inapplicableItems.refs.slice(inapplicableItems.i))
|
|
255
|
-
clientsStructRefs.clients.delete(client)
|
|
256
|
-
inapplicableItems.i = 0
|
|
257
|
-
inapplicableItems.refs = []
|
|
258
|
-
} else {
|
|
259
|
-
// item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue
|
|
260
|
-
restStructs.clients.set(client, [item])
|
|
261
|
-
}
|
|
262
|
-
// remove client from clientsStructRefsIds to prevent users from applying the same update again
|
|
263
|
-
clientsStructRefsIds = clientsStructRefsIds.filter(c => c !== client)
|
|
264
|
-
}
|
|
265
|
-
stack.length = 0
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// iterate over all struct readers until we are done
|
|
269
|
-
while (true) {
|
|
270
|
-
if (stackHead.constructor !== Skip) {
|
|
271
|
-
const localClock = map.setIfUndefined(state, stackHead.id.client, () => getState(store, stackHead.id.client))
|
|
272
|
-
const offset = localClock - stackHead.id.clock
|
|
273
|
-
const missing = stackHead.getMissing(transaction, store)
|
|
274
|
-
if (missing !== null) {
|
|
275
|
-
stack.push(stackHead)
|
|
276
|
-
// get the struct reader that has the missing struct
|
|
277
|
-
/**
|
|
278
|
-
* @type {{ refs: Array<GC|Item>, i: number }}
|
|
279
|
-
*/
|
|
280
|
-
const structRefs = clientsStructRefs.clients.get(/** @type {number} */ (missing)) || { refs: [], i: 0 }
|
|
281
|
-
if (structRefs.refs.length === structRefs.i || missing === stackHead.id.client || stack.some(s => s.id.client === missing)) { // @todo this could be optimized!
|
|
282
|
-
// This update message causally depends on another update message that doesn't exist yet
|
|
283
|
-
updateMissingSv(/** @type {number} */ (missing), getState(store, missing))
|
|
284
|
-
addStackToRestSS()
|
|
285
|
-
} else {
|
|
286
|
-
stackHead = structRefs.refs[structRefs.i++]
|
|
287
|
-
continue
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
// all fine, apply the stackhead
|
|
291
|
-
// but first add a skip to structs if necessary
|
|
292
|
-
if (offset < 0) {
|
|
293
|
-
const skip = new Skip(createID(stackHead.id.client, localClock), -offset)
|
|
294
|
-
skip.integrate(transaction, 0)
|
|
295
|
-
}
|
|
296
|
-
stackHead.integrate(transaction, 0)
|
|
297
|
-
state.set(stackHead.id.client, math.max(stackHead.id.clock + stackHead.length, localClock))
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// iterate to next stackHead
|
|
301
|
-
if (stack.length > 0) {
|
|
302
|
-
stackHead = /** @type {GC|Item} */ (stack.pop())
|
|
303
|
-
} else if (curStructsTarget !== null && curStructsTarget.i < curStructsTarget.refs.length) {
|
|
304
|
-
stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
|
|
305
|
-
} else {
|
|
306
|
-
curStructsTarget = getNextStructTarget()
|
|
307
|
-
if (curStructsTarget === null) {
|
|
308
|
-
// we are done!
|
|
309
|
-
break
|
|
310
|
-
} else {
|
|
311
|
-
stackHead = /** @type {GC|Item} */ (curStructsTarget.refs[curStructsTarget.i++])
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (restStructs.clients.size > 0) {
|
|
316
|
-
const encoder = new UpdateEncoderV2()
|
|
317
|
-
writeClientsStructs(encoder, restStructs, new Map())
|
|
318
|
-
// write empty deleteset
|
|
319
|
-
// writeDeleteSet(encoder, new DeleteSet())
|
|
320
|
-
encoding.writeVarUint(encoder.restEncoder, 0) // => no need for an extra function call, just write 0 deletes
|
|
321
|
-
return { missing: missingSV, update: encoder.toUint8Array() }
|
|
322
|
-
}
|
|
323
|
-
return null
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
|
328
|
-
* @param {Transaction} transaction
|
|
329
|
-
*
|
|
330
|
-
* @private
|
|
331
|
-
* @function
|
|
332
|
-
*/
|
|
333
|
-
export const writeStructsFromTransaction = (encoder, transaction) => writeStructsFromIdSet(encoder, transaction.doc.store, transaction.insertSet)
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Read and apply a document update.
|
|
337
|
-
*
|
|
338
|
-
* This function has the same effect as `applyUpdate` but accepts a decoder.
|
|
339
|
-
*
|
|
340
|
-
* @param {decoding.Decoder} decoder
|
|
341
|
-
* @param {Doc} ydoc
|
|
342
|
-
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
|
|
343
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} [structDecoder]
|
|
344
|
-
*
|
|
345
|
-
* @function
|
|
346
|
-
*/
|
|
347
|
-
export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = new UpdateDecoderV2(decoder)) =>
|
|
348
|
-
transact(ydoc, transaction => {
|
|
349
|
-
// force that transaction.local is set to non-local
|
|
350
|
-
transaction.local = false
|
|
351
|
-
let retry = false
|
|
352
|
-
const doc = transaction.doc
|
|
353
|
-
const store = doc.store
|
|
354
|
-
// let start = performance.now()
|
|
355
|
-
const ss = readStructSet(structDecoder, doc)
|
|
356
|
-
const knownState = createIdSet()
|
|
357
|
-
ss.clients.forEach((_, client) => {
|
|
358
|
-
const storeStructs = store.clients.get(client)
|
|
359
|
-
if (storeStructs) {
|
|
360
|
-
const last = storeStructs[storeStructs.length - 1]
|
|
361
|
-
knownState.add(client, 0, last.id.clock + last.length)
|
|
362
|
-
// remove known items from ss
|
|
363
|
-
store.skips.clients.get(client)?.getIds().forEach(idrange => {
|
|
364
|
-
knownState.delete(client, idrange.clock, idrange.len)
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
})
|
|
368
|
-
// remove known items from ss
|
|
369
|
-
removeRangesFromStructSet(ss, knownState)
|
|
370
|
-
// console.log('time to read structs: ', performance.now() - start) // @todo remove
|
|
371
|
-
// start = performance.now()
|
|
372
|
-
// console.log('time to merge: ', performance.now() - start) // @todo remove
|
|
373
|
-
// start = performance.now()
|
|
374
|
-
const restStructs = integrateStructs(transaction, store, ss)
|
|
375
|
-
const pending = store.pendingStructs
|
|
376
|
-
if (pending) {
|
|
377
|
-
// check if we can apply something
|
|
378
|
-
for (const [client, clock] of pending.missing) {
|
|
379
|
-
if (ss.clients.has(client) || clock < getState(store, client)) {
|
|
380
|
-
retry = true
|
|
381
|
-
break
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (restStructs) {
|
|
385
|
-
// merge restStructs into store.pending
|
|
386
|
-
for (const [client, clock] of restStructs.missing) {
|
|
387
|
-
const mclock = pending.missing.get(client)
|
|
388
|
-
if (mclock == null || mclock > clock) {
|
|
389
|
-
pending.missing.set(client, clock)
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
pending.update = mergeUpdatesV2([pending.update, restStructs.update])
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
store.pendingStructs = restStructs
|
|
396
|
-
}
|
|
397
|
-
// console.log('time to integrate: ', performance.now() - start) // @todo remove
|
|
398
|
-
// start = performance.now()
|
|
399
|
-
const dsRest = readAndApplyDeleteSet(structDecoder, transaction, store)
|
|
400
|
-
if (store.pendingDs) {
|
|
401
|
-
// @todo we could make a lower-bound state-vector check as we do above
|
|
402
|
-
const pendingDSUpdate = new UpdateDecoderV2(decoding.createDecoder(store.pendingDs))
|
|
403
|
-
decoding.readVarUint(pendingDSUpdate.restDecoder) // read 0 structs, because we only encode deletes in pendingdsupdate
|
|
404
|
-
const dsRest2 = readAndApplyDeleteSet(pendingDSUpdate, transaction, store)
|
|
405
|
-
if (dsRest && dsRest2) {
|
|
406
|
-
// case 1: ds1 != null && ds2 != null
|
|
407
|
-
store.pendingDs = mergeUpdatesV2([dsRest, dsRest2])
|
|
408
|
-
} else {
|
|
409
|
-
// case 2: ds1 != null
|
|
410
|
-
// case 3: ds2 != null
|
|
411
|
-
// case 4: ds1 == null && ds2 == null
|
|
412
|
-
store.pendingDs = dsRest || dsRest2
|
|
413
|
-
}
|
|
414
|
-
} else {
|
|
415
|
-
// Either dsRest == null && pendingDs == null OR dsRest != null
|
|
416
|
-
store.pendingDs = dsRest
|
|
417
|
-
}
|
|
418
|
-
// console.log('time to cleanup: ', performance.now() - start) // @todo remove
|
|
419
|
-
// start = performance.now()
|
|
420
|
-
|
|
421
|
-
// console.log('time to resume delete readers: ', performance.now() - start) // @todo remove
|
|
422
|
-
// start = performance.now()
|
|
423
|
-
if (retry) {
|
|
424
|
-
const update = /** @type {{update: Uint8Array}} */ (store.pendingStructs).update
|
|
425
|
-
store.pendingStructs = null
|
|
426
|
-
applyUpdateV2(transaction.doc, update)
|
|
427
|
-
}
|
|
428
|
-
}, transactionOrigin, false)
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Read and apply a document update.
|
|
432
|
-
*
|
|
433
|
-
* This function has the same effect as `applyUpdate` but accepts a decoder.
|
|
434
|
-
*
|
|
435
|
-
* @param {decoding.Decoder} decoder
|
|
436
|
-
* @param {Doc} ydoc
|
|
437
|
-
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
|
|
438
|
-
*
|
|
439
|
-
* @function
|
|
440
|
-
*/
|
|
441
|
-
export const readUpdate = (decoder, ydoc, transactionOrigin) => readUpdateV2(decoder, ydoc, transactionOrigin, new UpdateDecoderV1(decoder))
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
|
|
445
|
-
*
|
|
446
|
-
* This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
|
|
447
|
-
*
|
|
448
|
-
* @param {Doc} ydoc
|
|
449
|
-
* @param {Uint8Array} update
|
|
450
|
-
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
|
|
451
|
-
* @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
|
|
452
|
-
*
|
|
453
|
-
* @function
|
|
454
|
-
*/
|
|
455
|
-
export const applyUpdateV2 = (ydoc, update, transactionOrigin, YDecoder = UpdateDecoderV2) => {
|
|
456
|
-
const decoder = decoding.createDecoder(update)
|
|
457
|
-
readUpdateV2(decoder, ydoc, transactionOrigin, new YDecoder(decoder))
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
|
|
462
|
-
*
|
|
463
|
-
* This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
|
|
464
|
-
*
|
|
465
|
-
* @param {Doc} ydoc
|
|
466
|
-
* @param {Uint8Array} update
|
|
467
|
-
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
|
|
468
|
-
*
|
|
469
|
-
* @function
|
|
470
|
-
*/
|
|
471
|
-
export const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateV2(ydoc, update, transactionOrigin, UpdateDecoderV1)
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will
|
|
475
|
-
* only write the operations that are missing.
|
|
476
|
-
*
|
|
477
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
|
478
|
-
* @param {Doc} doc
|
|
479
|
-
* @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs
|
|
480
|
-
*
|
|
481
|
-
* @function
|
|
482
|
-
*/
|
|
483
|
-
export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) => {
|
|
484
|
-
writeClientsStructs(encoder, doc.store, targetStateVector)
|
|
485
|
-
writeIdSet(encoder, doc.store.ds)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
|
|
490
|
-
* only write the operations that are missing.
|
|
491
|
-
*
|
|
492
|
-
* Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
|
|
493
|
-
*
|
|
494
|
-
* @param {Doc} doc
|
|
495
|
-
* @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
|
|
496
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} [encoder]
|
|
497
|
-
* @return {Uint8Array<ArrayBuffer>}
|
|
498
|
-
*
|
|
499
|
-
* @function
|
|
500
|
-
*/
|
|
501
|
-
export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector = new Uint8Array([0]), encoder = new UpdateEncoderV2()) => {
|
|
502
|
-
const targetStateVector = decodeStateVector(encodedTargetStateVector)
|
|
503
|
-
writeStateAsUpdate(encoder, doc, targetStateVector)
|
|
504
|
-
const updates = [encoder.toUint8Array()]
|
|
505
|
-
// also add the pending updates (if there are any)
|
|
506
|
-
if (doc.store.pendingDs) {
|
|
507
|
-
updates.push(doc.store.pendingDs)
|
|
508
|
-
}
|
|
509
|
-
if (doc.store.pendingStructs) {
|
|
510
|
-
updates.push(diffUpdateV2(doc.store.pendingStructs.update, encodedTargetStateVector))
|
|
511
|
-
}
|
|
512
|
-
if (updates.length > 1) {
|
|
513
|
-
if (encoder.constructor === UpdateEncoderV1) {
|
|
514
|
-
return mergeUpdates(updates.map((update, i) => i === 0 ? update : convertUpdateFormatV2ToV1(update)))
|
|
515
|
-
} else if (encoder.constructor === UpdateEncoderV2) {
|
|
516
|
-
return mergeUpdatesV2(updates)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return updates[0]
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
|
|
524
|
-
* only write the operations that are missing.
|
|
525
|
-
*
|
|
526
|
-
* Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
|
|
527
|
-
*
|
|
528
|
-
* @param {Doc} doc
|
|
529
|
-
* @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
|
|
530
|
-
* @return {Uint8Array<ArrayBuffer>}
|
|
531
|
-
*
|
|
532
|
-
* @function
|
|
533
|
-
*/
|
|
534
|
-
export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => encodeStateAsUpdateV2(doc, encodedTargetStateVector, new UpdateEncoderV1())
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Read state vector from Decoder and return as Map
|
|
538
|
-
*
|
|
539
|
-
* @param {DSDecoderV1 | DSDecoderV2} decoder
|
|
540
|
-
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
|
|
541
|
-
*
|
|
542
|
-
* @function
|
|
543
|
-
*/
|
|
544
|
-
export const readStateVector = decoder => {
|
|
545
|
-
const ss = new Map()
|
|
546
|
-
const ssLength = decoding.readVarUint(decoder.restDecoder)
|
|
547
|
-
for (let i = 0; i < ssLength; i++) {
|
|
548
|
-
const client = decoding.readVarUint(decoder.restDecoder)
|
|
549
|
-
const clock = decoding.readVarUint(decoder.restDecoder)
|
|
550
|
-
ss.set(client, clock)
|
|
551
|
-
}
|
|
552
|
-
return ss
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Read decodedState and return State as Map.
|
|
557
|
-
*
|
|
558
|
-
* @param {Uint8Array} decodedState
|
|
559
|
-
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
|
|
560
|
-
*
|
|
561
|
-
* @function
|
|
562
|
-
*/
|
|
563
|
-
// export const decodeStateVectorV2 = decodedState => readStateVector(new DSDecoderV2(decoding.createDecoder(decodedState)))
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Read decodedState and return State as Map.
|
|
567
|
-
*
|
|
568
|
-
* @param {Uint8Array} decodedState
|
|
569
|
-
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
|
|
570
|
-
*
|
|
571
|
-
* @function
|
|
572
|
-
*/
|
|
573
|
-
export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(decoding.createDecoder(decodedState)))
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
|
577
|
-
* @param {Map<number,number>} sv
|
|
578
|
-
* @function
|
|
579
|
-
*/
|
|
580
|
-
export const writeStateVector = (encoder, sv) => {
|
|
581
|
-
encoding.writeVarUint(encoder.restEncoder, sv.size)
|
|
582
|
-
array.from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
|
|
583
|
-
encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping
|
|
584
|
-
encoding.writeVarUint(encoder.restEncoder, clock)
|
|
585
|
-
})
|
|
586
|
-
return encoder
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
|
591
|
-
* @param {Doc} doc
|
|
592
|
-
*
|
|
593
|
-
* @function
|
|
594
|
-
*/
|
|
595
|
-
export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store))
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Encode State as Uint8Array.
|
|
599
|
-
*
|
|
600
|
-
* @param {Doc|Map<number,number>} doc
|
|
601
|
-
* @param {IdSetEncoderV1 | IdSetEncoderV2} [encoder]
|
|
602
|
-
* @return {Uint8Array}
|
|
603
|
-
*
|
|
604
|
-
* @function
|
|
605
|
-
*/
|
|
606
|
-
export const encodeStateVectorV2 = (doc, encoder = new IdSetEncoderV2()) => {
|
|
607
|
-
if (doc instanceof Map) {
|
|
608
|
-
writeStateVector(encoder, doc)
|
|
609
|
-
} else {
|
|
610
|
-
writeDocumentStateVector(encoder, doc)
|
|
611
|
-
}
|
|
612
|
-
return encoder.toUint8Array()
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* Encode State as Uint8Array.
|
|
617
|
-
*
|
|
618
|
-
* @param {Doc|Map<number,number>} doc
|
|
619
|
-
* @return {Uint8Array}
|
|
620
|
-
*
|
|
621
|
-
* @function
|
|
622
|
-
*/
|
|
623
|
-
export const encodeStateVector = doc => encodeStateVectorV2(doc, new IdSetEncoderV1())
|
package/src/utils/isParentOf.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { AbstractType, Item } from '../internals.js' // eslint-disable-line
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Check if `parent` is a parent of `child`.
|
|
5
|
-
*
|
|
6
|
-
* @param {import('../utils/types.js').YType} parent
|
|
7
|
-
* @param {Item|null} child
|
|
8
|
-
* @return {Boolean} Whether `parent` is a parent of `child`.
|
|
9
|
-
*
|
|
10
|
-
* @private
|
|
11
|
-
* @function
|
|
12
|
-
*/
|
|
13
|
-
export const isParentOf = (parent, child) => {
|
|
14
|
-
while (child !== null) {
|
|
15
|
-
if (child.parent === parent) {
|
|
16
|
-
return true
|
|
17
|
-
}
|
|
18
|
-
child = /** @type {AbstractType<any>} */ (child.parent)._item
|
|
19
|
-
}
|
|
20
|
-
return false
|
|
21
|
-
}
|
package/src/utils/logging.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AbstractType // eslint-disable-line
|
|
3
|
-
} from '../internals.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Convenient helper to log type information.
|
|
7
|
-
*
|
|
8
|
-
* Do not use in productive systems as the output can be immense!
|
|
9
|
-
*
|
|
10
|
-
* @param {AbstractType<any>} type
|
|
11
|
-
*/
|
|
12
|
-
export const logType = type => {
|
|
13
|
-
const res = []
|
|
14
|
-
let n = type._start
|
|
15
|
-
while (n) {
|
|
16
|
-
res.push(n)
|
|
17
|
-
n = n.right
|
|
18
|
-
}
|
|
19
|
-
console.log('Children: ', res)
|
|
20
|
-
console.log('Children content: ', res.filter(m => !m.deleted).map(m => m.content))
|
|
21
|
-
}
|
package/src/utils/types.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import('../types/YArray.js').YArray<any>
|
|
3
|
-
* | import('../types/YMap.js').YMap<any>
|
|
4
|
-
* | import('../types/YText.js').YText<any>
|
|
5
|
-
* | import('../types/YXmlFragment.js').YXmlFragment<any,any>
|
|
6
|
-
* | import('../types/YXmlElement.js').YXmlElement<any,any>
|
|
7
|
-
* | import('../types/YXmlHook.js').YXmlHook
|
|
8
|
-
* | import('../types/YXmlText.js').YXmlText} YValueType
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YValueType} YValue
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {import('../types/AbstractType.js').AbstractType<any,any>} YType
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {typeof import('../types/YArray.js').YArray<any>
|
|
21
|
-
* | typeof import('../types/YMap.js').YMap<any>
|
|
22
|
-
* | typeof import('../types/YText.js').YText<any>
|
|
23
|
-
* | typeof import('../types/YXmlFragment.js').YXmlFragment<any,any>
|
|
24
|
-
* | typeof import('../types/YXmlElement.js').YXmlElement<any,any>
|
|
25
|
-
* | typeof import('../types/YXmlHook.js').YXmlHook
|
|
26
|
-
* | typeof import('../types/YXmlText.js').YXmlText
|
|
27
|
-
* | typeof import('../types/AbstractType.js').AbstractType} YTypeConstructors
|
|
28
|
-
*/
|