@y/y 14.0.0-19 → 14.0.0-20

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 (129) 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 +19 -16
  15. package/dist/src/utils/IdMap.d.ts.map +1 -1
  16. package/dist/src/utils/IdSet.d.ts +2 -2
  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/Transaction.d.ts +9 -5
  21. package/dist/src/utils/Transaction.d.ts.map +1 -1
  22. package/dist/src/utils/UndoManager.d.ts +14 -12
  23. package/dist/src/utils/UndoManager.d.ts.map +1 -1
  24. package/dist/src/utils/UpdateEncoder.d.ts +2 -0
  25. package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
  26. package/dist/src/utils/YEvent.d.ts +21 -42
  27. package/dist/src/utils/YEvent.d.ts.map +1 -1
  28. package/dist/src/utils/isParentOf.d.ts +1 -1
  29. package/dist/src/utils/isParentOf.d.ts.map +1 -1
  30. package/dist/src/utils/logging.d.ts +2 -2
  31. package/dist/src/utils/logging.d.ts.map +1 -1
  32. package/dist/src/utils/meta.d.ts +43 -0
  33. package/dist/src/utils/meta.d.ts.map +1 -0
  34. package/dist/src/utils/ts.d.ts +4 -0
  35. package/dist/src/utils/ts.d.ts.map +1 -0
  36. package/dist/src/utils/updates.d.ts +3 -9
  37. package/dist/src/utils/updates.d.ts.map +1 -1
  38. package/dist/src/ytype.d.ts +498 -0
  39. package/dist/src/ytype.d.ts.map +1 -0
  40. package/dist/tests/IdMap.tests.d.ts.map +1 -1
  41. package/dist/tests/attribution.tests.d.ts +1 -0
  42. package/dist/tests/attribution.tests.d.ts.map +1 -1
  43. package/dist/tests/compatibility.tests.d.ts.map +1 -1
  44. package/dist/tests/doc.tests.d.ts.map +1 -1
  45. package/dist/tests/relativePositions.tests.d.ts +9 -9
  46. package/dist/tests/relativePositions.tests.d.ts.map +1 -1
  47. package/dist/tests/snapshot.tests.d.ts.map +1 -1
  48. package/dist/tests/testHelper.d.ts +28 -27
  49. package/dist/tests/testHelper.d.ts.map +1 -1
  50. package/dist/tests/undo-redo.tests.d.ts.map +1 -1
  51. package/dist/tests/updates.tests.d.ts +1 -1
  52. package/dist/tests/updates.tests.d.ts.map +1 -1
  53. package/dist/tests/y-array.tests.d.ts +0 -2
  54. package/dist/tests/y-array.tests.d.ts.map +1 -1
  55. package/dist/tests/y-map.tests.d.ts +0 -3
  56. package/dist/tests/y-map.tests.d.ts.map +1 -1
  57. package/dist/tests/y-text.tests.d.ts +1 -1
  58. package/dist/tests/y-text.tests.d.ts.map +1 -1
  59. package/dist/tests/y-xml.tests.d.ts +0 -1
  60. package/dist/tests/y-xml.tests.d.ts.map +1 -1
  61. package/package.json +16 -16
  62. package/src/index.js +152 -0
  63. package/src/internals.js +35 -0
  64. package/src/structs/AbstractStruct.js +59 -0
  65. package/src/structs/ContentAny.js +115 -0
  66. package/src/structs/ContentBinary.js +93 -0
  67. package/src/structs/ContentDeleted.js +101 -0
  68. package/src/structs/ContentDoc.js +141 -0
  69. package/src/structs/ContentEmbed.js +98 -0
  70. package/src/structs/ContentFormat.js +105 -0
  71. package/src/structs/ContentJSON.js +119 -0
  72. package/src/structs/ContentString.js +113 -0
  73. package/src/structs/ContentType.js +152 -0
  74. package/src/structs/GC.js +80 -0
  75. package/src/structs/Item.js +841 -0
  76. package/src/structs/Skip.js +75 -0
  77. package/src/utils/AttributionManager.js +653 -0
  78. package/src/utils/Doc.js +266 -0
  79. package/src/utils/EventHandler.js +87 -0
  80. package/src/utils/ID.js +89 -0
  81. package/src/utils/IdMap.js +673 -0
  82. package/src/utils/IdSet.js +825 -0
  83. package/src/utils/RelativePosition.js +352 -0
  84. package/src/utils/Snapshot.js +220 -0
  85. package/src/utils/StructSet.js +137 -0
  86. package/src/utils/StructStore.js +289 -0
  87. package/src/utils/Transaction.js +671 -0
  88. package/src/utils/UndoManager.js +406 -0
  89. package/src/utils/UpdateDecoder.js +281 -0
  90. package/src/utils/UpdateEncoder.js +327 -0
  91. package/src/utils/YEvent.js +189 -0
  92. package/src/utils/delta-helpers.js +54 -0
  93. package/src/utils/encoding.js +623 -0
  94. package/src/utils/isParentOf.js +21 -0
  95. package/src/utils/logging.js +21 -0
  96. package/src/utils/meta.js +97 -0
  97. package/src/utils/ts.js +3 -0
  98. package/src/utils/updates.js +711 -0
  99. package/src/ytype.js +1962 -0
  100. package/dist/Skip-CE05BUF8.js +0 -11875
  101. package/dist/Skip-CE05BUF8.js.map +0 -1
  102. package/dist/index-C21sDQ5u.js +0 -163
  103. package/dist/index-C21sDQ5u.js.map +0 -1
  104. package/dist/internals.js +0 -25
  105. package/dist/internals.js.map +0 -1
  106. package/dist/src/types/AbstractType.d.ts +0 -239
  107. package/dist/src/types/AbstractType.d.ts.map +0 -1
  108. package/dist/src/types/YArray.d.ts +0 -128
  109. package/dist/src/types/YArray.d.ts.map +0 -1
  110. package/dist/src/types/YMap.d.ts +0 -112
  111. package/dist/src/types/YMap.d.ts.map +0 -1
  112. package/dist/src/types/YText.d.ts +0 -216
  113. package/dist/src/types/YText.d.ts.map +0 -1
  114. package/dist/src/types/YXmlElement.d.ts +0 -106
  115. package/dist/src/types/YXmlElement.d.ts.map +0 -1
  116. package/dist/src/types/YXmlFragment.d.ts +0 -143
  117. package/dist/src/types/YXmlFragment.d.ts.map +0 -1
  118. package/dist/src/types/YXmlHook.d.ts +0 -32
  119. package/dist/src/types/YXmlHook.d.ts.map +0 -1
  120. package/dist/src/types/YXmlText.d.ts +0 -34
  121. package/dist/src/types/YXmlText.d.ts.map +0 -1
  122. package/dist/src/utils/AbstractConnector.d.ts +0 -20
  123. package/dist/src/utils/AbstractConnector.d.ts.map +0 -1
  124. package/dist/src/utils/types.d.ts +0 -7
  125. package/dist/src/utils/types.d.ts.map +0 -1
  126. package/dist/testHelper.js +0 -617
  127. package/dist/testHelper.js.map +0 -1
  128. package/dist/yjs.js +0 -26
  129. package/dist/yjs.js.map +0 -1
@@ -0,0 +1,352 @@
1
+ import {
2
+ writeID,
3
+ readID,
4
+ compareIDs,
5
+ getState,
6
+ findRootTypeKey,
7
+ Item,
8
+ createID,
9
+ ContentType,
10
+ followRedone,
11
+ getItem,
12
+ StructStore, ID, Doc, YType, noAttributionsManager, // eslint-disable-line
13
+ } from '../internals.js'
14
+
15
+ import * as encoding from 'lib0/encoding'
16
+ import * as decoding from 'lib0/decoding'
17
+ import * as error from 'lib0/error'
18
+
19
+ /**
20
+ * A relative position is based on the Yjs model and is not affected by document changes.
21
+ * E.g. If you place a relative position before a certain character, it will always point to this character.
22
+ * If you place a relative position at the end of a type, it will always point to the end of the type.
23
+ *
24
+ * A numeric position is often unsuited for user selections, because it does not change when content is inserted
25
+ * before or after.
26
+ *
27
+ * ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position.
28
+ *
29
+ * One of the properties must be defined.
30
+ *
31
+ * @example
32
+ * // Current cursor position is at position 10
33
+ * const relativePosition = createRelativePositionFromIndex(yText, 10)
34
+ * // modify yText
35
+ * yText.insert(0, 'abc')
36
+ * yText.delete(3, 10)
37
+ * // Compute the cursor position
38
+ * const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition)
39
+ * absolutePosition.type === yText // => true
40
+ * console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3
41
+ *
42
+ */
43
+ export class RelativePosition {
44
+ /**
45
+ * @param {ID|null} type
46
+ * @param {string|null} tname
47
+ * @param {ID|null} item
48
+ * @param {number} assoc
49
+ */
50
+ constructor (type, tname, item, assoc = 0) {
51
+ /**
52
+ * @type {ID|null}
53
+ */
54
+ this.type = type
55
+ /**
56
+ * @type {string|null}
57
+ */
58
+ this.tname = tname
59
+ /**
60
+ * @type {ID | null}
61
+ */
62
+ this.item = item
63
+ /**
64
+ * A relative position is associated to a specific character. By default
65
+ * assoc >= 0, the relative position is associated to the character
66
+ * after the meant position.
67
+ * I.e. position 1 in 'ab' is associated to character 'b'.
68
+ *
69
+ * If assoc < 0, then the relative position is associated to the character
70
+ * before the meant position.
71
+ *
72
+ * @type {number}
73
+ */
74
+ this.assoc = assoc
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {RelativePosition} rpos
80
+ * @return {any}
81
+ */
82
+ export const relativePositionToJSON = rpos => {
83
+ const json = {}
84
+ if (rpos.type) {
85
+ json.type = rpos.type
86
+ }
87
+ if (rpos.tname) {
88
+ json.tname = rpos.tname
89
+ }
90
+ if (rpos.item) {
91
+ json.item = rpos.item
92
+ }
93
+ if (rpos.assoc != null) {
94
+ json.assoc = rpos.assoc
95
+ }
96
+ return json
97
+ }
98
+
99
+ /**
100
+ * @param {any} json
101
+ * @return {RelativePosition}
102
+ *
103
+ * @function
104
+ */
105
+ export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname ?? null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc)
106
+
107
+ export class AbsolutePosition {
108
+ /**
109
+ * @param {YType<any>} type
110
+ * @param {number} index
111
+ * @param {number} [assoc]
112
+ */
113
+ constructor (type, index, assoc = 0) {
114
+ /**
115
+ * @type {YType<any>}
116
+ */
117
+ this.type = type
118
+ /**
119
+ * @type {number}
120
+ */
121
+ this.index = index
122
+ this.assoc = assoc
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @param {YType<any>} type
128
+ * @param {number} index
129
+ * @param {number} [assoc]
130
+ *
131
+ * @function
132
+ */
133
+ export const createAbsolutePosition = (type, index, assoc = 0) => new AbsolutePosition(type, index, assoc)
134
+
135
+ /**
136
+ * @param {YType<any>} type
137
+ * @param {ID|null} item
138
+ * @param {number} [assoc]
139
+ *
140
+ * @function
141
+ */
142
+ export const createRelativePosition = (type, item, assoc) => {
143
+ let typeid = null
144
+ let tname = null
145
+ if (type._item === null) {
146
+ tname = findRootTypeKey(type)
147
+ } else {
148
+ typeid = createID(type._item.id.client, type._item.id.clock)
149
+ }
150
+ return new RelativePosition(typeid, tname, item, assoc)
151
+ }
152
+
153
+ /**
154
+ * Create a relativePosition based on a absolute position.
155
+ *
156
+ * @param {YType} type The base type (e.g. YText or YArray).
157
+ * @param {number} index The absolute position.
158
+ * @param {number} [assoc]
159
+ * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
160
+ * @return {RelativePosition}
161
+ *
162
+ * @function
163
+ */
164
+ export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, attributionManager = noAttributionsManager) => {
165
+ let t = type._start
166
+ if (assoc < 0) {
167
+ // associated to the left character or the beginning of a type, increment index if possible.
168
+ if (index === 0) {
169
+ return createRelativePosition(type, null, assoc)
170
+ }
171
+ index--
172
+ }
173
+ while (t !== null) {
174
+ const len = attributionManager.contentLength(t)
175
+ if (len > index) {
176
+ // case 1: found position somewhere in the linked list
177
+ return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc)
178
+ }
179
+ index -= len
180
+ if (t.right === null && assoc < 0) {
181
+ // left-associated position, return last available id
182
+ return createRelativePosition(type, t.lastId, assoc)
183
+ }
184
+ t = t.right
185
+ }
186
+ return createRelativePosition(type, null, assoc)
187
+ }
188
+
189
+ /**
190
+ * @param {encoding.Encoder} encoder
191
+ * @param {RelativePosition} rpos
192
+ *
193
+ * @function
194
+ */
195
+ export const writeRelativePosition = (encoder, rpos) => {
196
+ const { type, tname, item, assoc } = rpos
197
+ if (item !== null) {
198
+ encoding.writeVarUint(encoder, 0)
199
+ writeID(encoder, item)
200
+ } else if (tname !== null) {
201
+ // case 2: found position at the end of the list and type is stored in y.share
202
+ encoding.writeUint8(encoder, 1)
203
+ encoding.writeVarString(encoder, tname)
204
+ } else if (type !== null) {
205
+ // case 3: found position at the end of the list and type is attached to an item
206
+ encoding.writeUint8(encoder, 2)
207
+ writeID(encoder, type)
208
+ } else {
209
+ throw error.unexpectedCase()
210
+ }
211
+ encoding.writeVarInt(encoder, assoc)
212
+ return encoder
213
+ }
214
+
215
+ /**
216
+ * @param {RelativePosition} rpos
217
+ * @return {Uint8Array}
218
+ */
219
+ export const encodeRelativePosition = rpos => {
220
+ const encoder = encoding.createEncoder()
221
+ writeRelativePosition(encoder, rpos)
222
+ return encoding.toUint8Array(encoder)
223
+ }
224
+
225
+ /**
226
+ * @param {decoding.Decoder} decoder
227
+ * @return {RelativePosition}
228
+ *
229
+ * @function
230
+ */
231
+ export const readRelativePosition = decoder => {
232
+ let type = null
233
+ let tname = null
234
+ let itemID = null
235
+ switch (decoding.readVarUint(decoder)) {
236
+ case 0:
237
+ // case 1: found position somewhere in the linked list
238
+ itemID = readID(decoder)
239
+ break
240
+ case 1:
241
+ // case 2: found position at the end of the list and type is stored in y.share
242
+ tname = decoding.readVarString(decoder)
243
+ break
244
+ case 2: {
245
+ // case 3: found position at the end of the list and type is attached to an item
246
+ type = readID(decoder)
247
+ }
248
+ }
249
+ const assoc = decoding.hasContent(decoder) ? decoding.readVarInt(decoder) : 0
250
+ return new RelativePosition(type, tname, itemID, assoc)
251
+ }
252
+
253
+ /**
254
+ * @param {Uint8Array} uint8Array
255
+ * @return {RelativePosition}
256
+ */
257
+ export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
258
+
259
+ /**
260
+ * @param {StructStore} store
261
+ * @param {ID} id
262
+ */
263
+ const getItemWithOffset = (store, id) => {
264
+ const item = getItem(store, id)
265
+ const diff = id.clock - item.id.clock
266
+ return {
267
+ item, diff
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Transform a relative position to an absolute position.
273
+ *
274
+ * If you want to share the relative position with other users, you should set
275
+ * `followUndoneDeletions` to false to get consistent results across all clients.
276
+ *
277
+ * When calculating the absolute position, we try to follow the "undone deletions". This yields
278
+ * better results for the user who performed undo. However, only the user who performed the undo
279
+ * will get the better results, the other users don't know which operations recreated a deleted
280
+ * range of content. There is more information in this ticket: https://github.com/yjs/yjs/issues/638
281
+ *
282
+ * @param {RelativePosition} rpos
283
+ * @param {Doc} doc
284
+ * @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638
285
+ * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
286
+ * @return {AbsolutePosition|null}
287
+ *
288
+ * @function
289
+ */
290
+ export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true, attributionManager = noAttributionsManager) => {
291
+ const store = doc.store
292
+ const rightID = rpos.item
293
+ const typeID = rpos.type
294
+ const tname = rpos.tname
295
+ const assoc = rpos.assoc
296
+ let type = null
297
+ let index = 0
298
+ if (rightID !== null) {
299
+ if (getState(store, rightID.client) <= rightID.clock) {
300
+ return null
301
+ }
302
+ const res = followUndoneDeletions ? followRedone(store, rightID) : getItemWithOffset(store, rightID)
303
+ const right = res.item
304
+ if (!(right instanceof Item)) {
305
+ return null
306
+ }
307
+ type = /** @type {YType<any>} */ (right.parent)
308
+ if (type._item === null || !type._item.deleted) {
309
+ index = attributionManager.contentLength(right) === 0 ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary
310
+ let n = right.left
311
+ while (n !== null) {
312
+ index += attributionManager.contentLength(n)
313
+ n = n.left
314
+ }
315
+ }
316
+ } else {
317
+ if (tname !== null) {
318
+ type = doc.get(tname)
319
+ } else if (typeID !== null) {
320
+ if (getState(store, typeID.client) <= typeID.clock) {
321
+ // type does not exist yet
322
+ return null
323
+ }
324
+ const { item } = followUndoneDeletions ? followRedone(store, typeID) : { item: getItem(store, typeID) }
325
+ if (item instanceof Item && item.content instanceof ContentType) {
326
+ type = item.content.type
327
+ } else {
328
+ // struct is garbage collected
329
+ return null
330
+ }
331
+ } else {
332
+ throw error.unexpectedCase()
333
+ }
334
+ if (assoc >= 0) {
335
+ index = type._length
336
+ } else {
337
+ index = 0
338
+ }
339
+ }
340
+ return createAbsolutePosition(type, index, rpos.assoc)
341
+ }
342
+
343
+ /**
344
+ * @param {RelativePosition|null} a
345
+ * @param {RelativePosition|null} b
346
+ * @return {boolean}
347
+ *
348
+ * @function
349
+ */
350
+ export const compareRelativePositions = (a, b) => a === b || (
351
+ a !== null && b !== null && a.tname === b.tname && compareIDs(a.item, b.item) && compareIDs(a.type, b.type) && a.assoc === b.assoc
352
+ )
@@ -0,0 +1,220 @@
1
+ import {
2
+ createDeleteSetFromStructStore,
3
+ getStateVector,
4
+ getItemCleanStart,
5
+ iterateStructsByIdSet,
6
+ writeIdSet,
7
+ writeStateVector,
8
+ readIdSet,
9
+ readStateVector,
10
+ createIdSet,
11
+ createID,
12
+ getState,
13
+ findIndexSS,
14
+ UpdateEncoderV2,
15
+ applyUpdateV2,
16
+ LazyStructReader,
17
+ equalIdSets,
18
+ UpdateDecoderV1, UpdateDecoderV2, IdSetEncoderV1, IdSetEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, IdSet, Item, // eslint-disable-line
19
+ mergeIdSets
20
+ } from '../internals.js'
21
+
22
+ import * as map from 'lib0/map'
23
+ import * as set from 'lib0/set'
24
+ import * as decoding from 'lib0/decoding'
25
+ import * as encoding from 'lib0/encoding'
26
+
27
+ export class Snapshot {
28
+ /**
29
+ * @param {IdSet} ds
30
+ * @param {Map<number,number>} sv state map
31
+ */
32
+ constructor (ds, sv) {
33
+ /**
34
+ * @type {IdSet}
35
+ */
36
+ this.ds = ds
37
+ /**
38
+ * State Map
39
+ * @type {Map<number,number>}
40
+ */
41
+ this.sv = sv
42
+ }
43
+ }
44
+
45
+ /**
46
+ * @param {Snapshot} snap1
47
+ * @param {Snapshot} snap2
48
+ * @return {boolean}
49
+ */
50
+ export const equalSnapshots = (snap1, snap2) => {
51
+ const sv1 = snap1.sv
52
+ const sv2 = snap2.sv
53
+ if (sv1.size !== sv2.size) {
54
+ return false
55
+ }
56
+ for (const [key, value] of sv1.entries()) {
57
+ if (sv2.get(key) !== value) {
58
+ return false
59
+ }
60
+ }
61
+ return equalIdSets(snap1.ds, snap2.ds)
62
+ }
63
+
64
+ /**
65
+ * @param {Snapshot} snapshot
66
+ * @param {IdSetEncoderV1 | IdSetEncoderV2} [encoder]
67
+ * @return {Uint8Array}
68
+ */
69
+ export const encodeSnapshotV2 = (snapshot, encoder = new IdSetEncoderV2()) => {
70
+ writeIdSet(encoder, snapshot.ds)
71
+ writeStateVector(encoder, snapshot.sv)
72
+ return encoder.toUint8Array()
73
+ }
74
+
75
+ /**
76
+ * @param {Snapshot} snapshot
77
+ * @return {Uint8Array}
78
+ */
79
+ export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new IdSetEncoderV1())
80
+
81
+ /**
82
+ * @param {Uint8Array} buf
83
+ * @param {DSDecoderV1 | DSDecoderV2} [decoder]
84
+ * @return {Snapshot}
85
+ */
86
+ export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createDecoder(buf))) => {
87
+ return new Snapshot(readIdSet(decoder), readStateVector(decoder))
88
+ }
89
+
90
+ /**
91
+ * @param {Uint8Array} buf
92
+ * @return {Snapshot}
93
+ */
94
+ export const decodeSnapshot = buf => decodeSnapshotV2(buf, new DSDecoderV1(decoding.createDecoder(buf)))
95
+
96
+ /**
97
+ * @param {IdSet} ds
98
+ * @param {Map<number,number>} sm
99
+ * @return {Snapshot}
100
+ */
101
+ export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
102
+
103
+ export const emptySnapshot = createSnapshot(createIdSet(), new Map())
104
+
105
+ /**
106
+ * @param {Doc} doc
107
+ * @return {Snapshot}
108
+ */
109
+ export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
110
+
111
+ /**
112
+ * @param {Item} item
113
+ * @param {Snapshot|undefined} snapshot
114
+ *
115
+ * @protected
116
+ * @function
117
+ */
118
+ export const isVisible = (item, snapshot) => snapshot === undefined
119
+ ? !item.deleted
120
+ : snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !snapshot.ds.hasId(item.id)
121
+
122
+ /**
123
+ * @param {Transaction} transaction
124
+ * @param {Snapshot} snapshot
125
+ */
126
+ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
127
+ const meta = map.setIfUndefined(transaction.meta, splitSnapshotAffectedStructs, set.create)
128
+ const store = transaction.doc.store
129
+ // check if we already split for this snapshot
130
+ if (!meta.has(snapshot)) {
131
+ snapshot.sv.forEach((clock, client) => {
132
+ if (clock < getState(store, client)) {
133
+ getItemCleanStart(transaction, createID(client, clock))
134
+ }
135
+ })
136
+ iterateStructsByIdSet(transaction, snapshot.ds, _item => {})
137
+ meta.add(snapshot)
138
+ }
139
+ }
140
+
141
+ /**
142
+ * @example
143
+ * const ydoc = new Y.Doc({ gc: false })
144
+ * ydoc.getText().insert(0, 'world!')
145
+ * const snapshot = Y.snapshot(ydoc)
146
+ * ydoc.getText().insert(0, 'hello ')
147
+ * const restored = Y.createDocFromSnapshot(ydoc, snapshot)
148
+ * assert(restored.getText().toString() === 'world!')
149
+ *
150
+ * @param {Doc} originDoc
151
+ * @param {Snapshot} snapshot
152
+ * @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
153
+ * @return {Doc}
154
+ */
155
+ export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
156
+ if (originDoc.gc) {
157
+ // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
158
+ throw new Error('Garbage-collection must be disabled in `originDoc`!')
159
+ }
160
+ const { sv, ds } = snapshot
161
+
162
+ const encoder = new UpdateEncoderV2()
163
+ originDoc.transact(transaction => {
164
+ let size = 0
165
+ sv.forEach(clock => {
166
+ if (clock > 0) {
167
+ size++
168
+ }
169
+ })
170
+ encoding.writeVarUint(encoder.restEncoder, size)
171
+ // splitting the structs before writing them to the encoder
172
+ for (const [client, clock] of sv) {
173
+ if (clock === 0) {
174
+ continue
175
+ }
176
+ if (clock < getState(originDoc.store, client)) {
177
+ getItemCleanStart(transaction, createID(client, clock))
178
+ }
179
+ const structs = originDoc.store.clients.get(client) || []
180
+ const lastStructIndex = findIndexSS(structs, clock - 1)
181
+ // write # encoded structs
182
+ encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
183
+ encoder.writeClient(client)
184
+ // first clock written is 0
185
+ encoding.writeVarUint(encoder.restEncoder, 0)
186
+ for (let i = 0; i <= lastStructIndex; i++) {
187
+ structs[i].write(encoder, 0, 0)
188
+ }
189
+ }
190
+ writeIdSet(encoder, ds)
191
+ })
192
+
193
+ applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
194
+ return newDoc
195
+ }
196
+
197
+ /**
198
+ * @param {Snapshot} snapshot
199
+ * @param {Uint8Array} update
200
+ * @param {typeof UpdateDecoderV2 | typeof UpdateDecoderV1} [YDecoder]
201
+ */
202
+ export const snapshotContainsUpdateV2 = (snapshot, update, YDecoder = UpdateDecoderV2) => {
203
+ const structs = []
204
+ const updateDecoder = new YDecoder(decoding.createDecoder(update))
205
+ const lazyDecoder = new LazyStructReader(updateDecoder, false)
206
+ for (let curr = lazyDecoder.curr; curr !== null; curr = lazyDecoder.next()) {
207
+ structs.push(curr)
208
+ if ((snapshot.sv.get(curr.id.client) || 0) < curr.id.clock + curr.length) {
209
+ return false
210
+ }
211
+ }
212
+ const mergedDS = mergeIdSets([snapshot.ds, readIdSet(updateDecoder)])
213
+ return equalIdSets(snapshot.ds, mergedDS)
214
+ }
215
+
216
+ /**
217
+ * @param {Snapshot} snapshot
218
+ * @param {Uint8Array} update
219
+ */
220
+ export const snapshotContainsUpdate = (snapshot, update) => snapshotContainsUpdateV2(snapshot, update, UpdateDecoderV1)
@@ -0,0 +1,137 @@
1
+ import {
2
+ createID,
3
+ readItemContent,
4
+ findIndexCleanStart,
5
+ Skip,
6
+ UpdateDecoderV1, UpdateDecoderV2, IdSet, Doc, GC, Item, ID, // eslint-disable-line
7
+ } from '../internals.js'
8
+
9
+ import * as decoding from 'lib0/decoding'
10
+ import * as binary from 'lib0/binary'
11
+ import * as map from 'lib0/map'
12
+
13
+ /**
14
+ * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder The decoder object to read data from.
15
+ * @param {Doc} doc
16
+ * @return {StructSet}
17
+ *
18
+ * @private
19
+ * @function
20
+ */
21
+ export const readStructSet = (decoder, doc) => {
22
+ const clientRefs = new StructSet()
23
+ const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder)
24
+ for (let i = 0; i < numOfStateUpdates; i++) {
25
+ const numberOfStructs = decoding.readVarUint(decoder.restDecoder)
26
+ /**
27
+ * @type {Array<GC|Item>}
28
+ */
29
+ const refs = new Array(numberOfStructs)
30
+ const client = decoder.readClient()
31
+ let clock = decoding.readVarUint(decoder.restDecoder)
32
+ clientRefs.clients.set(client, new StructRange(refs))
33
+ for (let i = 0; i < numberOfStructs; i++) {
34
+ const info = decoder.readInfo()
35
+ switch (binary.BITS5 & info) {
36
+ case 0: { // GC
37
+ const len = decoder.readLen()
38
+ refs[i] = new GC(createID(client, clock), len)
39
+ clock += len
40
+ break
41
+ }
42
+ case 10: { // Skip Struct (nothing to apply)
43
+ // @todo we could reduce the amount of checks by adding Skip struct to clientRefs so we know that something is missing.
44
+ const len = decoding.readVarUint(decoder.restDecoder)
45
+ refs[i] = new Skip(createID(client, clock), len)
46
+ clock += len
47
+ break
48
+ }
49
+ default: { // Item with content
50
+ /**
51
+ * The optimized implementation doesn't use any variables because inlining variables is faster.
52
+ * Below a non-optimized version is shown that implements the basic algorithm with
53
+ * a few comments
54
+ */
55
+ const cantCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
56
+ // If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
57
+ // and we read the next string as parentYKey.
58
+ // It indicates how we store/retrieve parent from `y.share`
59
+ // @type {string|null}
60
+ const struct = new Item(
61
+ createID(client, clock),
62
+ null, // left
63
+ (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin
64
+ null, // right
65
+ (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
66
+ cantCopyParentInfo ? (decoder.readParentInfo() ? doc.get(decoder.readString()) : decoder.readLeftID()) : null, // parent
67
+ cantCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
68
+ readItemContent(decoder, info) // item content
69
+ )
70
+ refs[i] = struct
71
+ clock += struct.length
72
+ }
73
+ }
74
+ }
75
+ }
76
+ return clientRefs
77
+ }
78
+
79
+ /**
80
+ * Remove item-ranges from the StructSet.
81
+ *
82
+ * @param {StructSet} ss
83
+ * @param {IdSet} exclude
84
+ */
85
+ export const removeRangesFromStructSet = (ss, exclude) => {
86
+ // @todo walk through ss instead to reduce iterations
87
+ exclude.clients.forEach((range, client) => {
88
+ const structs = /** @type {StructRange} */ (ss.clients.get(client))?.refs
89
+ if (structs != null) {
90
+ const firstStruct = structs[0]
91
+ const lastStruct = structs[structs.length - 1]
92
+ const idranges = range.getIds()
93
+ for (let i = 0; i < idranges.length; i++) {
94
+ const range = idranges[i]
95
+ let startIndex = 0
96
+ if (range.clock >= lastStruct.id.clock + lastStruct.length) continue
97
+ if (range.clock > firstStruct.id.clock) {
98
+ startIndex = findIndexCleanStart(null, structs, range.clock)
99
+ }
100
+ let endIndex = structs.length // must be set here, after structs is modified
101
+ if (range.clock + range.len <= firstStruct.id.clock) continue
102
+ if (range.clock + range.len < lastStruct.id.clock + lastStruct.length) {
103
+ endIndex = findIndexCleanStart(null, structs, range.clock + range.len)
104
+ }
105
+ if (startIndex < endIndex) {
106
+ structs[startIndex] = new Skip(new ID(client, range.clock), range.len)
107
+ const d = endIndex - startIndex
108
+ if (d > 1) {
109
+ structs.splice(startIndex + 1, d - 1)
110
+ }
111
+ }
112
+ }
113
+ }
114
+ })
115
+ }
116
+
117
+ class StructRange {
118
+ /**
119
+ * @param {Array<Item|GC>} refs
120
+ */
121
+ constructor (refs) {
122
+ this.i = 0
123
+ /**
124
+ * @type {Array<Item | GC>}
125
+ */
126
+ this.refs = refs
127
+ }
128
+ }
129
+
130
+ export class StructSet {
131
+ constructor () {
132
+ /**
133
+ * @type {Map<number, StructRange>}
134
+ */
135
+ this.clients = map.create()
136
+ }
137
+ }