@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.
Files changed (135) hide show
  1. package/README.md +7 -5
  2. package/dist/src/index.d.ts +2 -1
  3. package/dist/src/internals.d.ts +2 -9
  4. package/dist/src/structs/ContentType.d.ts +6 -12
  5. package/dist/src/structs/ContentType.d.ts.map +1 -1
  6. package/dist/src/structs/Item.d.ts +5 -6
  7. package/dist/src/structs/Item.d.ts.map +1 -1
  8. package/dist/src/utils/AttributionManager.d.ts +16 -14
  9. package/dist/src/utils/AttributionManager.d.ts.map +1 -1
  10. package/dist/src/utils/Doc.d.ts +7 -70
  11. package/dist/src/utils/Doc.d.ts.map +1 -1
  12. package/dist/src/utils/ID.d.ts +2 -2
  13. package/dist/src/utils/ID.d.ts.map +1 -1
  14. package/dist/src/utils/IdMap.d.ts +22 -19
  15. package/dist/src/utils/IdMap.d.ts.map +1 -1
  16. package/dist/src/utils/IdSet.d.ts +6 -6
  17. package/dist/src/utils/IdSet.d.ts.map +1 -1
  18. package/dist/src/utils/RelativePosition.d.ts +8 -8
  19. package/dist/src/utils/RelativePosition.d.ts.map +1 -1
  20. package/dist/src/utils/Snapshot.d.ts +3 -3
  21. package/dist/src/utils/Snapshot.d.ts.map +1 -1
  22. package/dist/src/utils/Transaction.d.ts +9 -5
  23. package/dist/src/utils/Transaction.d.ts.map +1 -1
  24. package/dist/src/utils/UndoManager.d.ts +14 -12
  25. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  26. package/dist/src/utils/UpdateDecoder.d.ts +8 -4
  27. package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
  28. package/dist/src/utils/UpdateEncoder.d.ts +2 -0
  29. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  30. package/dist/src/utils/YEvent.d.ts +21 -42
  31. package/dist/src/utils/YEvent.d.ts.map +1 -1
  32. package/dist/src/utils/encoding.d.ts +3 -3
  33. package/dist/src/utils/encoding.d.ts.map +1 -1
  34. package/dist/src/utils/isParentOf.d.ts +1 -1
  35. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  36. package/dist/src/utils/logging.d.ts +2 -2
  37. package/dist/src/utils/logging.d.ts.map +1 -1
  38. package/dist/src/utils/meta.d.ts +71 -0
  39. package/dist/src/utils/meta.d.ts.map +1 -0
  40. package/dist/src/utils/ts.d.ts +4 -0
  41. package/dist/src/utils/ts.d.ts.map +1 -0
  42. package/dist/src/utils/updates.d.ts +11 -11
  43. package/dist/src/utils/updates.d.ts.map +1 -1
  44. package/dist/src/ytype.d.ts +498 -0
  45. package/dist/src/ytype.d.ts.map +1 -0
  46. package/dist/tests/IdMap.tests.d.ts.map +1 -1
  47. package/dist/tests/attribution.tests.d.ts +1 -0
  48. package/dist/tests/attribution.tests.d.ts.map +1 -1
  49. package/dist/tests/compatibility.tests.d.ts.map +1 -1
  50. package/dist/tests/doc.tests.d.ts.map +1 -1
  51. package/dist/tests/relativePositions.tests.d.ts +9 -9
  52. package/dist/tests/relativePositions.tests.d.ts.map +1 -1
  53. package/dist/tests/snapshot.tests.d.ts.map +1 -1
  54. package/dist/tests/testHelper.d.ts +28 -27
  55. package/dist/tests/testHelper.d.ts.map +1 -1
  56. package/dist/tests/undo-redo.tests.d.ts.map +1 -1
  57. package/dist/tests/updates.tests.d.ts +2 -1
  58. package/dist/tests/updates.tests.d.ts.map +1 -1
  59. package/dist/tests/y-array.tests.d.ts +0 -2
  60. package/dist/tests/y-array.tests.d.ts.map +1 -1
  61. package/dist/tests/y-map.tests.d.ts +0 -3
  62. package/dist/tests/y-map.tests.d.ts.map +1 -1
  63. package/dist/tests/y-text.tests.d.ts +1 -1
  64. package/dist/tests/y-text.tests.d.ts.map +1 -1
  65. package/dist/tests/y-xml.tests.d.ts +0 -1
  66. package/dist/tests/y-xml.tests.d.ts.map +1 -1
  67. package/package.json +16 -16
  68. package/src/index.js +156 -0
  69. package/src/internals.js +35 -0
  70. package/src/structs/AbstractStruct.js +59 -0
  71. package/src/structs/ContentAny.js +115 -0
  72. package/src/structs/ContentBinary.js +93 -0
  73. package/src/structs/ContentDeleted.js +101 -0
  74. package/src/structs/ContentDoc.js +141 -0
  75. package/src/structs/ContentEmbed.js +98 -0
  76. package/src/structs/ContentFormat.js +105 -0
  77. package/src/structs/ContentJSON.js +119 -0
  78. package/src/structs/ContentString.js +113 -0
  79. package/src/structs/ContentType.js +152 -0
  80. package/src/structs/GC.js +80 -0
  81. package/src/structs/Item.js +841 -0
  82. package/src/structs/Skip.js +75 -0
  83. package/src/utils/AttributionManager.js +653 -0
  84. package/src/utils/Doc.js +266 -0
  85. package/src/utils/EventHandler.js +87 -0
  86. package/src/utils/ID.js +89 -0
  87. package/src/utils/IdMap.js +673 -0
  88. package/src/utils/IdSet.js +825 -0
  89. package/src/utils/RelativePosition.js +352 -0
  90. package/src/utils/Snapshot.js +220 -0
  91. package/src/utils/StructSet.js +137 -0
  92. package/src/utils/StructStore.js +289 -0
  93. package/src/utils/Transaction.js +671 -0
  94. package/src/utils/UndoManager.js +406 -0
  95. package/src/utils/UpdateDecoder.js +285 -0
  96. package/src/utils/UpdateEncoder.js +327 -0
  97. package/src/utils/YEvent.js +189 -0
  98. package/src/utils/delta-helpers.js +54 -0
  99. package/src/utils/encoding.js +623 -0
  100. package/src/utils/isParentOf.js +21 -0
  101. package/src/utils/logging.js +21 -0
  102. package/src/utils/meta.js +190 -0
  103. package/src/utils/ts.js +3 -0
  104. package/src/utils/updates.js +802 -0
  105. package/src/ytype.js +1962 -0
  106. package/dist/Skip-CE05BUF8.js +0 -11875
  107. package/dist/Skip-CE05BUF8.js.map +0 -1
  108. package/dist/index-C21sDQ5u.js +0 -163
  109. package/dist/index-C21sDQ5u.js.map +0 -1
  110. package/dist/internals.js +0 -25
  111. package/dist/internals.js.map +0 -1
  112. package/dist/src/types/AbstractType.d.ts +0 -239
  113. package/dist/src/types/AbstractType.d.ts.map +0 -1
  114. package/dist/src/types/YArray.d.ts +0 -128
  115. package/dist/src/types/YArray.d.ts.map +0 -1
  116. package/dist/src/types/YMap.d.ts +0 -112
  117. package/dist/src/types/YMap.d.ts.map +0 -1
  118. package/dist/src/types/YText.d.ts +0 -216
  119. package/dist/src/types/YText.d.ts.map +0 -1
  120. package/dist/src/types/YXmlElement.d.ts +0 -106
  121. package/dist/src/types/YXmlElement.d.ts.map +0 -1
  122. package/dist/src/types/YXmlFragment.d.ts +0 -143
  123. package/dist/src/types/YXmlFragment.d.ts.map +0 -1
  124. package/dist/src/types/YXmlHook.d.ts +0 -32
  125. package/dist/src/types/YXmlHook.d.ts.map +0 -1
  126. package/dist/src/types/YXmlText.d.ts +0 -34
  127. package/dist/src/types/YXmlText.d.ts.map +0 -1
  128. package/dist/src/utils/AbstractConnector.d.ts +0 -20
  129. package/dist/src/utils/AbstractConnector.d.ts.map +0 -1
  130. package/dist/src/utils/types.d.ts +0 -7
  131. package/dist/src/utils/types.d.ts.map +0 -1
  132. package/dist/testHelper.js +0 -617
  133. package/dist/testHelper.js.map +0 -1
  134. package/dist/yjs.js +0 -26
  135. package/dist/yjs.js.map +0 -1
@@ -0,0 +1,802 @@
1
+ import * as binary from 'lib0/binary'
2
+ import * as decoding from 'lib0/decoding'
3
+ import * as encoding from 'lib0/encoding'
4
+ import * as error from 'lib0/error'
5
+ import * as f from 'lib0/function'
6
+ import * as logging from 'lib0/logging'
7
+ import * as map from 'lib0/map'
8
+ import * as math from 'lib0/math'
9
+ import * as string from 'lib0/string'
10
+
11
+ import {
12
+ ContentAny,
13
+ ContentBinary,
14
+ ContentDeleted,
15
+ ContentDoc,
16
+ ContentEmbed,
17
+ ContentFormat,
18
+ ContentJSON,
19
+ ContentString,
20
+ ContentType,
21
+ createID,
22
+ decodeStateVector,
23
+ IdSetEncoderV1,
24
+ IdSetEncoderV2,
25
+ GC,
26
+ Item,
27
+ mergeIdSets,
28
+ readIdSet,
29
+ readItemContent,
30
+ Skip,
31
+ UpdateDecoderV1,
32
+ UpdateDecoderV2,
33
+ UpdateEncoderV1,
34
+ UpdateEncoderV2,
35
+ writeIdSet,
36
+ createIdSet,
37
+ Doc,
38
+ applyUpdate,
39
+ applyUpdateV2
40
+ } from '../internals.js'
41
+
42
+ import * as idset from './IdSet.js'
43
+
44
+ /**
45
+ * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
46
+ */
47
+ function * lazyStructReaderGenerator (decoder) {
48
+ const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder)
49
+ for (let i = 0; i < numOfStateUpdates; i++) {
50
+ const numberOfStructs = decoding.readVarUint(decoder.restDecoder)
51
+ const client = decoder.readClient()
52
+ let clock = decoding.readVarUint(decoder.restDecoder)
53
+ for (let i = 0; i < numberOfStructs; i++) {
54
+ const info = decoder.readInfo()
55
+ // @todo use switch instead of ifs
56
+ if (info === 10) {
57
+ const len = decoding.readVarUint(decoder.restDecoder)
58
+ yield new Skip(createID(client, clock), len)
59
+ clock += len
60
+ } else if ((binary.BITS5 & info) !== 0) {
61
+ const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
62
+ // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
63
+ // and we read the next string as parentYKey.
64
+ // It indicates how we store/retrieve parent from `y.share`
65
+ // @type {string|null}
66
+ const struct = new Item(
67
+ createID(client, clock),
68
+ null, // left
69
+ (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin
70
+ null, // right
71
+ (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
72
+ // @ts-ignore Force writing a string here.
73
+ cantCopyParentInfo ? (decoder.readParentInfo() ? decoder.readString() : decoder.readLeftID()) : null, // parent
74
+ cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
75
+ readItemContent(decoder, info) // item content
76
+ )
77
+ yield struct
78
+ clock += struct.length
79
+ } else {
80
+ const len = decoder.readLen()
81
+ yield new GC(createID(client, clock), len)
82
+ clock += len
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ export class LazyStructReader {
89
+ /**
90
+ * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
91
+ * @param {boolean} filterSkips
92
+ */
93
+ constructor (decoder, filterSkips) {
94
+ this.gen = lazyStructReaderGenerator(decoder)
95
+ /**
96
+ * @type {null | Item | Skip | GC}
97
+ */
98
+ this.curr = null
99
+ this.done = false
100
+ this.filterSkips = filterSkips
101
+ this.next()
102
+ }
103
+
104
+ /**
105
+ * @return {Item | GC | Skip |null}
106
+ */
107
+ next () {
108
+ // ignore "Skip" structs
109
+ do {
110
+ this.curr = this.gen.next().value || null
111
+ } while (this.filterSkips && this.curr !== null && this.curr.constructor === Skip)
112
+ return this.curr
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @param {Uint8Array} update
118
+ *
119
+ */
120
+ export const logUpdate = update => logUpdateV2(update, UpdateDecoderV1)
121
+
122
+ /**
123
+ * @param {Uint8Array} update
124
+ * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
125
+ */
126
+ export const logUpdateV2 = (update, YDecoder = UpdateDecoderV2) => {
127
+ const structs = []
128
+ const updateDecoder = new YDecoder(decoding.createDecoder(update))
129
+ const lazyDecoder = new LazyStructReader(updateDecoder, false)
130
+ for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
131
+ structs.push(curr)
132
+ }
133
+ logging.print('Structs: ', structs)
134
+ const ds = readIdSet(updateDecoder)
135
+ logging.print('DeleteSet: ', ds)
136
+ }
137
+
138
+ /**
139
+ * @param {Uint8Array} update
140
+ *
141
+ */
142
+ export const decodeUpdate = (update) => decodeUpdateV2(update, UpdateDecoderV1)
143
+
144
+ /**
145
+ * @param {Uint8Array} update
146
+ * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
147
+ *
148
+ */
149
+ export const decodeUpdateV2 = (update, YDecoder = UpdateDecoderV2) => {
150
+ const structs = []
151
+ const updateDecoder = new YDecoder(decoding.createDecoder(update))
152
+ const lazyDecoder = new LazyStructReader(updateDecoder, false)
153
+ for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
154
+ structs.push(curr)
155
+ }
156
+ return {
157
+ structs,
158
+ ds: readIdSet(updateDecoder)
159
+ }
160
+ }
161
+
162
+ export class LazyStructWriter {
163
+ /**
164
+ * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
165
+ */
166
+ constructor (encoder) {
167
+ this.currClient = 0
168
+ this.startClock = 0
169
+ this.written = 0
170
+ this.encoder = encoder
171
+ /**
172
+ * We want to write operations lazily, but also we need to know beforehand how many operations we want to write for each client.
173
+ *
174
+ * This kind of meta-information (#clients, #structs-per-client-written) is written to the restEncoder.
175
+ *
176
+ * We fragment the restEncoder and store a slice of it per-client until we know how many clients there are.
177
+ * When we flush (toUint8Array) we write the restEncoder using the fragments and the meta-information.
178
+ *
179
+ * @type {Array<{ written: number, restEncoder: Uint8Array }>}
180
+ */
181
+ this.clientStructs = []
182
+ }
183
+ }
184
+
185
+ /**
186
+ * @param {Array<Uint8Array<ArrayBuffer>>} updates
187
+ * @return {Uint8Array<ArrayBuffer>}
188
+ */
189
+ export const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, UpdateEncoderV1)
190
+
191
+ /**
192
+ * @param {Uint8Array} update
193
+ * @param {typeof IdSetEncoderV1 | typeof IdSetEncoderV2} YEncoder
194
+ * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} YDecoder
195
+ * @return {Uint8Array}
196
+ */
197
+ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = IdSetEncoderV2, YDecoder = UpdateDecoderV2) => {
198
+ const encoder = new YEncoder()
199
+ const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update)), false)
200
+ let curr = updateDecoder.curr
201
+ if (curr !== null) {
202
+ let size = 0
203
+ let currClient = curr.id.client
204
+ let stopCounting = curr.id.clock !== 0 // must start at 0
205
+ let currClock = stopCounting ? 0 : curr.id.clock + curr.length
206
+ for (; curr !== null; curr = updateDecoder.next()) {
207
+ if (currClient !== curr.id.client) {
208
+ if (currClock !== 0) {
209
+ size++
210
+ // We found a new client
211
+ // write what we have to the encoder
212
+ encoding.writeVarUint(encoder.restEncoder, currClient)
213
+ encoding.writeVarUint(encoder.restEncoder, currClock)
214
+ }
215
+ currClient = curr.id.client
216
+ currClock = 0
217
+ stopCounting = curr.id.clock !== 0
218
+ }
219
+ // we ignore skips
220
+ if (curr.constructor === Skip) {
221
+ stopCounting = true
222
+ }
223
+ if (!stopCounting) {
224
+ currClock = curr.id.clock + curr.length
225
+ }
226
+ }
227
+ // write what we have
228
+ if (currClock !== 0) {
229
+ size++
230
+ encoding.writeVarUint(encoder.restEncoder, currClient)
231
+ encoding.writeVarUint(encoder.restEncoder, currClock)
232
+ }
233
+ // prepend the size of the state vector
234
+ const enc = encoding.createEncoder()
235
+ encoding.writeVarUint(enc, size)
236
+ encoding.writeBinaryEncoder(enc, encoder.restEncoder)
237
+ encoder.restEncoder = enc
238
+ return encoder.toUint8Array()
239
+ } else {
240
+ encoding.writeVarUint(encoder.restEncoder, 0)
241
+ return encoder.toUint8Array()
242
+ }
243
+ }
244
+
245
+ /**
246
+ * @param {Uint8Array} update
247
+ * @return {Uint8Array}
248
+ */
249
+ export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdateV2(update, IdSetEncoderV1, UpdateDecoderV1)
250
+
251
+ /**
252
+ * @param {Uint8Array} update
253
+ * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
254
+ * @return {import('./meta.js').ContentIds}
255
+ */
256
+ export const createContentIdsFromUpdateV2 = (update, YDecoder = UpdateDecoderV2) => {
257
+ const updateDecoder = new YDecoder(decoding.createDecoder(update))
258
+ const lazyDecoder = new LazyStructReader(updateDecoder, true)
259
+ const inserts = createIdSet()
260
+ let lastClientId = -1
261
+ let lastClock = 0
262
+ let lastLen = 0
263
+ for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
264
+ const currId = curr.id
265
+ if (lastClientId === currId.client && lastClock + lastLen === currId.clock) {
266
+ // default case: extend prev entry
267
+ lastLen += curr.length
268
+ } else {
269
+ if (lastClientId >= 0) {
270
+ inserts.add(lastClientId, lastClock, lastLen)
271
+ }
272
+ lastClientId = currId.client
273
+ lastClock = currId.clock
274
+ lastLen = curr.length
275
+ }
276
+ }
277
+ if (lastClientId >= 0) {
278
+ inserts.add(lastClientId, lastClock, lastLen)
279
+ }
280
+ const deletes = readIdSet(updateDecoder)
281
+ return { inserts, deletes }
282
+ }
283
+
284
+ /**
285
+ * @param {Uint8Array} update
286
+ * @return {import('./meta.js').ContentIds}
287
+ */
288
+ export const createContentIdsFromUpdate = update => createContentIdsFromUpdateV2(update, UpdateDecoderV1)
289
+
290
+ /**
291
+ * This method is intended to slice any kind of struct and retrieve the right part.
292
+ * It does not handle side-effects, so it should only be used by the lazy-encoder.
293
+ *
294
+ * @param {Item | GC | Skip} left
295
+ * @param {number} diff
296
+ * @return {Item | GC}
297
+ */
298
+ const sliceStruct = (left, diff) => {
299
+ if (left.constructor === GC) {
300
+ const { client, clock } = left.id
301
+ return new GC(createID(client, clock + diff), left.length - diff)
302
+ } else if (left.constructor === Skip) {
303
+ const { client, clock } = left.id
304
+ return new Skip(createID(client, clock + diff), left.length - diff)
305
+ } else {
306
+ const leftItem = /** @type {Item} */ (left)
307
+ const { client, clock } = leftItem.id
308
+ return new Item(
309
+ createID(client, clock + diff),
310
+ null,
311
+ createID(client, clock + diff - 1),
312
+ null,
313
+ leftItem.rightOrigin,
314
+ leftItem.parent,
315
+ leftItem.parentSub,
316
+ leftItem.content.splice(diff)
317
+ )
318
+ }
319
+ }
320
+
321
+ /**
322
+ *
323
+ * This function works similarly to `readUpdateV2`.
324
+ *
325
+ * @param {Array<Uint8Array<ArrayBuffer>>} updates
326
+ * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
327
+ * @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
328
+ * @return {Uint8Array<ArrayBuffer>}
329
+ */
330
+ export const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
331
+ if (updates.length === 1) {
332
+ return updates[0]
333
+ }
334
+ const updateDecoders = updates.map(update => new YDecoder(decoding.createDecoder(update)))
335
+ let lazyStructDecoders = updateDecoders.map(decoder => new LazyStructReader(decoder, true))
336
+
337
+ /**
338
+ * @todo we don't need offset because we always slice before
339
+ * @type {null | { struct: Item | GC | Skip, offset: number }}
340
+ */
341
+ let currWrite = null
342
+
343
+ const updateEncoder = new YEncoder()
344
+ // write structs lazily
345
+ const lazyStructEncoder = new LazyStructWriter(updateEncoder)
346
+
347
+ // Note: We need to ensure that all lazyStructDecoders are fully consumed
348
+ // Note: Should merge document updates whenever possible - even from different updates
349
+ // Note: Should handle that some operations cannot be applied yet ()
350
+
351
+ while (true) {
352
+ // Write higher clients first ⇒ sort by clientID & clock and remove decoders without content
353
+ lazyStructDecoders = lazyStructDecoders.filter(dec => dec.curr !== null)
354
+ lazyStructDecoders.sort(
355
+ /** @type {function(any,any):number} */ (dec1, dec2) => {
356
+ if (dec1.curr.id.client === dec2.curr.id.client) {
357
+ const clockDiff = dec1.curr.id.clock - dec2.curr.id.clock
358
+ if (clockDiff === 0) {
359
+ // @todo remove references to skip since the structDecoders must filter Skips.
360
+ return dec1.curr.constructor === dec2.curr.constructor
361
+ ? 0
362
+ : dec1.curr.constructor === Skip ? 1 : -1 // we are filtering skips anyway.
363
+ } else {
364
+ return clockDiff
365
+ }
366
+ } else {
367
+ return dec2.curr.id.client - dec1.curr.id.client
368
+ }
369
+ }
370
+ )
371
+ if (lazyStructDecoders.length === 0) {
372
+ break
373
+ }
374
+ const currDecoder = lazyStructDecoders[0]
375
+ // write from currDecoder until the next operation is from another client or if filler-struct
376
+ // then we need to reorder the decoders and find the next operation to write
377
+ const firstClient = /** @type {Item | GC} */ (currDecoder.curr).id.client
378
+
379
+ if (currWrite !== null) {
380
+ let curr = /** @type {Item | GC | null} */ (currDecoder.curr)
381
+ let iterated = false
382
+
383
+ // iterate until we find something that we haven't written already
384
+ // remember: first the high client-ids are written
385
+ while (curr !== null && curr.id.clock + curr.length <= currWrite.struct.id.clock + currWrite.struct.length && curr.id.client >= currWrite.struct.id.client) {
386
+ curr = currDecoder.next()
387
+ iterated = true
388
+ }
389
+ if (
390
+ curr === null || // current decoder is empty
391
+ curr.id.client !== firstClient || // check whether there is another decoder that has has updates from `firstClient`
392
+ (iterated && curr.id.clock > currWrite.struct.id.clock + currWrite.struct.length) // the above while loop was used and we are potentially missing updates
393
+ ) {
394
+ continue
395
+ }
396
+
397
+ if (firstClient !== currWrite.struct.id.client) {
398
+ writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset, 0)
399
+ currWrite = { struct: curr, offset: 0 }
400
+ currDecoder.next()
401
+ } else {
402
+ if (currWrite.struct.id.clock + currWrite.struct.length < curr.id.clock) {
403
+ // @todo write currStruct & set currStruct = Skip(clock = currStruct.id.clock + currStruct.length, length = curr.id.clock - self.clock)
404
+ if (currWrite.struct.constructor === Skip) {
405
+ // extend existing skip
406
+ currWrite.struct.length = curr.id.clock + curr.length - currWrite.struct.id.clock
407
+ } else {
408
+ writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset, 0)
409
+ const diff = curr.id.clock - currWrite.struct.id.clock - currWrite.struct.length
410
+ /**
411
+ * @type {Skip}
412
+ */
413
+ const struct = new Skip(createID(firstClient, currWrite.struct.id.clock + currWrite.struct.length), diff)
414
+ currWrite = { struct, offset: 0 }
415
+ }
416
+ } else { // if (currWrite.struct.id.clock + currWrite.struct.length >= curr.id.clock) {
417
+ const diff = currWrite.struct.id.clock + currWrite.struct.length - curr.id.clock
418
+ if (diff > 0) {
419
+ if (currWrite.struct.constructor === Skip) {
420
+ // prefer to slice Skip because the other struct might contain more information
421
+ currWrite.struct.length -= diff
422
+ } else {
423
+ curr = sliceStruct(curr, diff)
424
+ }
425
+ }
426
+ if (!currWrite.struct.mergeWith(/** @type {any} */ (curr))) {
427
+ writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset, 0)
428
+ currWrite = { struct: curr, offset: 0 }
429
+ currDecoder.next()
430
+ }
431
+ }
432
+ }
433
+ } else {
434
+ currWrite = { struct: /** @type {Item | GC} */ (currDecoder.curr), offset: 0 }
435
+ currDecoder.next()
436
+ }
437
+ for (
438
+ let next = currDecoder.curr;
439
+ next !== null && next.id.client === firstClient && next.id.clock === currWrite.struct.id.clock + currWrite.struct.length && next.constructor !== Skip;
440
+ next = currDecoder.next()
441
+ ) {
442
+ writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset, 0)
443
+ currWrite = { struct: next, offset: 0 }
444
+ }
445
+ }
446
+ if (currWrite !== null) {
447
+ writeStructToLazyStructWriter(lazyStructEncoder, currWrite.struct, currWrite.offset, 0)
448
+ currWrite = null
449
+ }
450
+ finishLazyStructWriting(lazyStructEncoder)
451
+
452
+ const dss = updateDecoders.map(decoder => readIdSet(decoder))
453
+ const ds = mergeIdSets(dss)
454
+ writeIdSet(updateEncoder, ds)
455
+ return updateEncoder.toUint8Array()
456
+ }
457
+
458
+ /**
459
+ * @deprecated
460
+ * @param {Uint8Array} update
461
+ * @param {Uint8Array} sv
462
+ * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
463
+ * @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
464
+ */
465
+ export const diffUpdateV2 = (update, sv, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
466
+ const state = decodeStateVector(sv)
467
+ const encoder = new YEncoder()
468
+ const lazyStructWriter = new LazyStructWriter(encoder)
469
+ const decoder = new YDecoder(decoding.createDecoder(update))
470
+ const reader = new LazyStructReader(decoder, false)
471
+ while (reader.curr) {
472
+ const curr = reader.curr
473
+ const currClient = curr.id.client
474
+ const svClock = state.get(currClient) || 0
475
+ if (reader.curr.constructor === Skip) {
476
+ // the first written struct shouldn't be a skip
477
+ reader.next()
478
+ continue
479
+ }
480
+ if (curr.id.clock + curr.length > svClock) {
481
+ writeStructToLazyStructWriter(lazyStructWriter, curr, math.max(svClock - curr.id.clock, 0), 0)
482
+ reader.next()
483
+ while (reader.curr && reader.curr.id.client === currClient) {
484
+ writeStructToLazyStructWriter(lazyStructWriter, reader.curr, 0, 0)
485
+ reader.next()
486
+ }
487
+ } else {
488
+ // read until something new comes up
489
+ while (reader.curr && reader.curr.id.client === currClient && reader.curr.id.clock + reader.curr.length <= svClock) {
490
+ reader.next()
491
+ }
492
+ }
493
+ }
494
+ finishLazyStructWriting(lazyStructWriter)
495
+ // write ds
496
+ const ds = readIdSet(decoder)
497
+ writeIdSet(encoder, ds)
498
+ return encoder.toUint8Array()
499
+ }
500
+
501
+ /**
502
+ * @deprecated
503
+ * @todo remove this in favor of intersectupdate
504
+ *
505
+ * @param {Uint8Array} update
506
+ * @param {Uint8Array} sv
507
+ */
508
+ export const diffUpdate = (update, sv) => diffUpdateV2(update, sv, UpdateDecoderV1, UpdateEncoderV1)
509
+
510
+ /**
511
+ * @param {LazyStructWriter} lazyWriter
512
+ */
513
+ const flushLazyStructWriter = lazyWriter => {
514
+ if (lazyWriter.written > 0) {
515
+ lazyWriter.clientStructs.push({ written: lazyWriter.written, restEncoder: encoding.toUint8Array(lazyWriter.encoder.restEncoder) })
516
+ lazyWriter.encoder.restEncoder = encoding.createEncoder()
517
+ lazyWriter.written = 0
518
+ }
519
+ }
520
+
521
+ /**
522
+ * @param {LazyStructWriter} lazyWriter
523
+ * @param {Item | GC} struct
524
+ * @param {number} offset
525
+ * @param {number} offsetEnd
526
+ */
527
+ const writeStructToLazyStructWriter = (lazyWriter, struct, offset, offsetEnd) => {
528
+ // flush curr if we start another client
529
+ if (lazyWriter.written > 0 && lazyWriter.currClient !== struct.id.client) {
530
+ flushLazyStructWriter(lazyWriter)
531
+ }
532
+ if (lazyWriter.written === 0) {
533
+ lazyWriter.currClient = struct.id.client
534
+ // write next client
535
+ lazyWriter.encoder.writeClient(struct.id.client)
536
+ // write startClock
537
+ encoding.writeVarUint(lazyWriter.encoder.restEncoder, struct.id.clock + offset)
538
+ }
539
+ struct.write(lazyWriter.encoder, offset, offsetEnd)
540
+ lazyWriter.written++
541
+ }
542
+ /**
543
+ * Call this function when we collected all parts and want to
544
+ * put all the parts together. After calling this method,
545
+ * you can continue using the UpdateEncoder.
546
+ *
547
+ * @param {LazyStructWriter} lazyWriter
548
+ */
549
+ const finishLazyStructWriting = (lazyWriter) => {
550
+ flushLazyStructWriter(lazyWriter)
551
+
552
+ // this is a fresh encoder because we called flushCurr
553
+ const restEncoder = lazyWriter.encoder.restEncoder
554
+
555
+ /**
556
+ * Now we put all the fragments together.
557
+ * This works similarly to `writeClientsStructs`
558
+ */
559
+
560
+ // write # states that were updated - i.e. the clients
561
+ encoding.writeVarUint(restEncoder, lazyWriter.clientStructs.length)
562
+
563
+ for (let i = 0; i < lazyWriter.clientStructs.length; i++) {
564
+ const partStructs = lazyWriter.clientStructs[i]
565
+ /**
566
+ * Works similarly to `writeStructs`
567
+ */
568
+ // write # encoded structs
569
+ encoding.writeVarUint(restEncoder, partStructs.written)
570
+ // write the rest of the fragment
571
+ encoding.writeUint8Array(restEncoder, partStructs.restEncoder)
572
+ }
573
+ }
574
+
575
+ /**
576
+ * @param {Uint8Array} update
577
+ * @param {function(Item|GC|Skip):Item|GC|Skip} blockTransformer
578
+ * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} YDecoder
579
+ * @param {typeof UpdateEncoderV2 | typeof UpdateEncoderV1 } YEncoder
580
+ */
581
+ export const convertUpdateFormat = (update, blockTransformer, YDecoder, YEncoder) => {
582
+ const updateDecoder = new YDecoder(decoding.createDecoder(update))
583
+ const lazyDecoder = new LazyStructReader(updateDecoder, false)
584
+ const updateEncoder = new YEncoder()
585
+ const lazyWriter = new LazyStructWriter(updateEncoder)
586
+ for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
587
+ writeStructToLazyStructWriter(lazyWriter, blockTransformer(curr), 0, 0)
588
+ }
589
+ finishLazyStructWriting(lazyWriter)
590
+ const ds = readIdSet(updateDecoder)
591
+ writeIdSet(updateEncoder, ds)
592
+ return updateEncoder.toUint8Array()
593
+ }
594
+
595
+ /**
596
+ * @typedef {Object} ObfuscatorOptions
597
+ * @property {boolean} [ObfuscatorOptions.formatting=true]
598
+ * @property {boolean} [ObfuscatorOptions.subdocs=true]
599
+ * @property {boolean} [ObfuscatorOptions.name=true] Whether to obfuscate nodeName / hookName
600
+ */
601
+
602
+ /**
603
+ * @param {ObfuscatorOptions} obfuscator
604
+ */
605
+ const createObfuscator = ({ formatting = true, subdocs = true, name = true } = {}) => {
606
+ let i = 0
607
+ const mapKeyCache = map.create()
608
+ const nodeNameCache = map.create()
609
+ const formattingKeyCache = map.create()
610
+ const formattingValueCache = map.create()
611
+ formattingValueCache.set(null, null) // end of a formatting range should always be the end of a formatting range
612
+ /**
613
+ * @param {Item|GC|Skip} block
614
+ * @return {Item|GC|Skip}
615
+ */
616
+ return block => {
617
+ switch (block.constructor) {
618
+ case GC:
619
+ case Skip:
620
+ return block
621
+ case Item: {
622
+ const item = /** @type {Item} */ (block)
623
+ const content = item.content
624
+ switch (content.constructor) {
625
+ case ContentDeleted:
626
+ break
627
+ case ContentType: {
628
+ if (name) {
629
+ const type = /** @type {ContentType} */ (content).type
630
+ if (type.name != null) {
631
+ type.name = map.setIfUndefined(nodeNameCache, type.name, () => 'typename-' + i)
632
+ }
633
+ }
634
+ break
635
+ }
636
+ case ContentAny: {
637
+ const c = /** @type {ContentAny} */ (content)
638
+ c.arr = c.arr.map(() => i)
639
+ break
640
+ }
641
+ case ContentBinary: {
642
+ const c = /** @type {ContentBinary} */ (content)
643
+ c.content = new Uint8Array([i])
644
+ break
645
+ }
646
+ case ContentDoc: {
647
+ const c = /** @type {ContentDoc} */ (content)
648
+ if (subdocs) {
649
+ c.opts = {}
650
+ c.doc.guid = i + ''
651
+ }
652
+ break
653
+ }
654
+ case ContentEmbed: {
655
+ const c = /** @type {ContentEmbed} */ (content)
656
+ c.embed = {}
657
+ break
658
+ }
659
+ case ContentFormat: {
660
+ const c = /** @type {ContentFormat} */ (content)
661
+ if (formatting) {
662
+ c.key = map.setIfUndefined(formattingKeyCache, c.key, () => i + '')
663
+ c.value = map.setIfUndefined(formattingValueCache, c.value, () => ({ i }))
664
+ }
665
+ break
666
+ }
667
+ case ContentJSON: {
668
+ const c = /** @type {ContentJSON} */ (content)
669
+ c.arr = c.arr.map(() => i)
670
+ break
671
+ }
672
+ case ContentString: {
673
+ const c = /** @type {ContentString} */ (content)
674
+ c.str = string.repeat((i % 10) + '', c.str.length)
675
+ break
676
+ }
677
+ default:
678
+ // unknown content type
679
+ error.unexpectedCase()
680
+ }
681
+ if (item.parentSub) {
682
+ item.parentSub = map.setIfUndefined(mapKeyCache, item.parentSub, () => i + '')
683
+ }
684
+ i++
685
+ return block
686
+ }
687
+ default:
688
+ // unknown block-type
689
+ error.unexpectedCase()
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * This function obfuscates the content of a Yjs update. This is useful to share
696
+ * buggy Yjs documents while significantly limiting the possibility that a
697
+ * developer can on the user. Note that it might still be possible to deduce
698
+ * some information by analyzing the "structure" of the document or by analyzing
699
+ * the typing behavior using the CRDT-related metadata that is still kept fully
700
+ * intact.
701
+ *
702
+ * @param {Uint8Array} update
703
+ * @param {ObfuscatorOptions} [opts]
704
+ */
705
+ export const obfuscateUpdate = (update, opts) => convertUpdateFormat(update, createObfuscator(opts), UpdateDecoderV1, UpdateEncoderV1)
706
+
707
+ /**
708
+ * @param {Uint8Array} update
709
+ * @param {ObfuscatorOptions} [opts]
710
+ */
711
+ export const obfuscateUpdateV2 = (update, opts) => convertUpdateFormat(update, createObfuscator(opts), UpdateDecoderV2, UpdateEncoderV2)
712
+
713
+ /**
714
+ * @param {Uint8Array} update
715
+ */
716
+ export const convertUpdateFormatV1ToV2 = update => convertUpdateFormat(update, f.id, UpdateDecoderV1, UpdateEncoderV2)
717
+
718
+ /**
719
+ * @param {Uint8Array} update
720
+ */
721
+ export const convertUpdateFormatV2ToV1 = update => convertUpdateFormat(update, f.id, UpdateDecoderV2, UpdateEncoderV1)
722
+
723
+ /**
724
+ * Filter an update to only include content specified by a ContentIds pattern.
725
+ *
726
+ * This function extracts a subset of an update, keeping only the structs whose IDs
727
+ * are present in `contentIds.inserts` and only the delete set entries that are
728
+ * present in `contentIds.deletes`.
729
+ *
730
+ * Note: If a struct partially overlaps with the contentIds pattern, only the
731
+ * overlapping portion is included in the result.
732
+ *
733
+ * @param {Uint8Array} update
734
+ * @param {import('./meta.js').ContentIds} contentIds - Pattern specifying which content to include
735
+ * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
736
+ * @param {typeof UpdateEncoderV1 | typeof UpdateEncoderV2} [YEncoder]
737
+ * @return {Uint8Array}
738
+ */
739
+ export const intersectUpdateWithContentIdsV2 = (update, contentIds, YDecoder = UpdateDecoderV2, YEncoder = UpdateEncoderV2) => {
740
+ const { inserts, deletes } = contentIds
741
+ const encoder = new YEncoder()
742
+ const lazyStructWriter = new LazyStructWriter(encoder)
743
+ const decoder = new YDecoder(decoding.createDecoder(update))
744
+ const reader = new LazyStructReader(decoder, true)
745
+
746
+ while (reader.curr) {
747
+ const currClientId = reader.curr.id.client
748
+ let nextClock = reader.curr.id.clock
749
+ let firstWrite = false
750
+ while (reader.curr != null && reader.curr.id.client === currClientId) {
751
+ const curr = reader.curr
752
+ for (const slice of inserts.slice(currClientId, nextClock, curr.length)) {
753
+ if (slice.exists) {
754
+ const skipLen = slice.clock - nextClock
755
+ if (skipLen > 0 && firstWrite) {
756
+ // write missing skip
757
+ writeStructToLazyStructWriter(lazyStructWriter, new Skip(createID(currClientId, nextClock), skipLen), 0, 0)
758
+ }
759
+ // write sliced content
760
+ writeStructToLazyStructWriter(lazyStructWriter, curr, slice.clock - curr.id.clock, (curr.id.clock + curr.length) - (slice.clock + slice.len))
761
+ nextClock = slice.clock + slice.len
762
+ firstWrite = true
763
+ }
764
+ }
765
+ reader.next()
766
+ }
767
+ }
768
+ finishLazyStructWriting(lazyStructWriter)
769
+ // Filter the delete set to only include entries in contentIds.deletes
770
+ const ds = readIdSet(decoder)
771
+ const filteredDs = idset.intersectSets(ds, deletes)
772
+ writeIdSet(encoder, filteredDs)
773
+ return encoder.toUint8Array()
774
+ }
775
+
776
+ /**
777
+ * Filter an update (V1 format) to only include content specified by a ContentIds pattern.
778
+ *
779
+ * @param {Uint8Array} update
780
+ * @param {import('./meta.js').ContentIds} contentIds - Pattern specifying which content to include
781
+ * @return {Uint8Array}
782
+ */
783
+ export const intersectUpdateWithContentIds = (update, contentIds) =>
784
+ intersectUpdateWithContentIdsV2(update, contentIds, UpdateDecoderV1, UpdateEncoderV1)
785
+
786
+ /**
787
+ * @param {Uint8Array} update
788
+ */
789
+ export const createDocFromUpdate = update => {
790
+ const ydoc = new Doc()
791
+ applyUpdate(ydoc, update)
792
+ return ydoc
793
+ }
794
+
795
+ /**
796
+ * @param {Uint8Array} update
797
+ */
798
+ export const createDocFromUpdateV2 = update => {
799
+ const ydoc = new Doc()
800
+ applyUpdateV2(ydoc, update)
801
+ return ydoc
802
+ }