rework and document api

This commit is contained in:
Kevin Jahns
2019-05-07 13:44:23 +02:00
parent 77687d94e6
commit 8c36f67f0b
33 changed files with 622 additions and 427 deletions

View File

@@ -1,6 +1,6 @@
export {
Y,
Doc,
Transaction,
YArray as Array,
YMap as Map,
@@ -24,25 +24,23 @@ export {
ItemString,
ItemType,
AbstractType,
compareCursors,
Cursor,
createCursorFromTypeOffset,
createCursorFromJSON,
createAbsolutePositionFromCursor,
writeCursor,
readCursor,
RelativePosition,
createRelativePositionFromTypeIndex,
createRelativePositionFromJSON,
createAbsolutePositionFromRelativePosition,
compareRelativePositions,
writeRelativePosition,
readRelativePosition,
ID,
createID,
compareIDs,
getState,
getStates,
readStatesAsMap,
writeStates,
writeModel,
readModel,
Snapshot,
findRootTypeKey,
typeArrayToArraySnapshot,
typeListToArraySnapshot,
typeMapGetSnapshot,
iterateDeletedStructs
iterateDeletedStructs,
applyUpdate,
encodeStateAsUpdate,
encodeDocumentStateVector
} from './internals.js'

View File

@@ -2,12 +2,12 @@ export * from './utils/DeleteSet.js'
export * from './utils/EventHandler.js'
export * from './utils/ID.js'
export * from './utils/isParentOf.js'
export * from './utils/cursor.js'
export * from './utils/RelativePosition.js'
export * from './utils/Snapshot.js'
export * from './utils/StructStore.js'
export * from './utils/Transaction.js'
// export * from './utils/UndoManager.js'
export * from './utils/Y.js'
export * from './utils/Doc.js'
export * from './utils/YEvent.js'
export * from './types/AbstractType.js'

View File

@@ -17,7 +17,7 @@ import {
getItemType,
getItemCleanEnd,
getItemCleanStart,
YEvent, StructStore, ID, AbstractType, Y, Transaction // eslint-disable-line
YEvent, StructStore, ID, AbstractType, Transaction // eslint-disable-line
} from '../internals.js'
import * as error from 'lib0/error.js'
@@ -153,7 +153,7 @@ export class AbstractItem extends AbstractStruct {
* @private
*/
integrate (transaction) {
const store = transaction.y.store
const store = transaction.doc.store
const id = this.id
const parent = this.parent
const parentSub = this.parentSub
@@ -415,23 +415,19 @@ export class AbstractItem extends AbstractStruct {
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
*
* @private
*/
gcChildren (transaction, store) { }
gcChildren (store) { }
/**
* @todo remove transaction param
*
* @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
gc (transaction, store, parentGCd) {
gc (store, parentGCd) {
if (!this.deleted) {
throw error.unexpectedCase()
}
@@ -619,12 +615,16 @@ export const computeItemParams = (transaction, store, leftid, rightid, parentid,
case GC:
break
default:
// Edge case: toStruct is called with an offset > 0. In this case left is defined.
// Depending in which order structs arrive, left may be GC'd and the parent not
// deleted. This is why we check if left is GC'd. Strictly we probably don't have
// to check if right is GC'd, but we will in case we run into future issues
if (!parentItem.deleted && (left === null || left.constructor !== GC) && (right === null || right.constructor !== GC)) {
parent = parentItem.type
}
}
} else if (parentYKey !== null) {
parent = transaction.y.get(parentYKey)
parent = transaction.doc.get(parentYKey)
} else if (left !== null) {
if (left.constructor !== GC) {
parent = left.parent

View File

@@ -1,6 +1,6 @@
import {
Y, StructStore, ID, Transaction // eslint-disable-line
StructStore, ID, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line

View File

@@ -4,7 +4,7 @@ import {
AbstractStruct,
createID,
addStruct,
Y, StructStore, Transaction, ID // eslint-disable-line
StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js'
import * as decoding from 'lib0/decoding.js'
@@ -48,7 +48,7 @@ export class GC extends AbstractStruct {
* @param {Transaction} transaction
*/
integrate (transaction) {
addStruct(transaction.y.store, this)
addStruct(transaction.doc.store, this)
}
/**

View File

@@ -9,6 +9,7 @@ import {
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as buffer from 'lib0/buffer.js'
/**
* @private
@@ -27,7 +28,7 @@ export class ItemBinary extends AbstractItem {
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {ArrayBuffer} content
* @param {Uint8Array} content
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
@@ -54,7 +55,7 @@ export class ItemBinary extends AbstractItem {
*/
write (encoder, offset) {
super.write(encoder, offset, structBinaryRefNumber)
encoding.writePayload(encoder, this.content)
encoding.writeVarUint8Array(encoder, this.content)
}
}
@@ -70,9 +71,9 @@ export class ItemBinaryRef extends AbstractItemRef {
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {ArrayBuffer}
* @type {Uint8Array}
*/
this.content = decoding.readPayload(decoder)
this.content = buffer.copyUint8Array(decoding.readVarUint8Array(decoder))
}
/**
* @param {Transaction} transaction

View File

@@ -8,7 +8,7 @@ import {
splitItem,
addToDeleteSet,
mergeItemWith,
Y, StructStore, Transaction, ID, AbstractType // eslint-disable-line
StructStore, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -87,15 +87,14 @@ export class ItemDeleted extends AbstractItem {
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
gc (transaction, store, parentGCd) {
gc (store, parentGCd) {
if (parentGCd) {
super.gc(transaction, store, parentGCd)
super.gc(store, parentGCd)
}
}
/**

View File

@@ -67,7 +67,7 @@ export class ItemEmbedRef extends AbstractItemRef {
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {ArrayBuffer}
* @type {Object}
*/
this.embed = JSON.parse(decoding.readVarString(decoder))
}

View File

@@ -7,7 +7,7 @@ import {
changeItemRefOffset,
GC,
mergeItemWith,
Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'

View File

@@ -7,7 +7,7 @@ import {
changeItemRefOffset,
GC,
mergeItemWith,
Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'

View File

@@ -10,7 +10,7 @@ import {
readYXmlFragment,
readYXmlHook,
readYXmlText,
StructStore, Y, GC, Transaction, ID, AbstractType // eslint-disable-line
StructStore, GC, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
@@ -83,7 +83,7 @@ export class ItemType extends AbstractItem {
*/
integrate (transaction) {
super.integrate(transaction)
this.type._integrate(transaction.y, this)
this.type._integrate(transaction.doc, this)
}
/**
* @param {encoding.Encoder} encoder
@@ -129,19 +129,18 @@ export class ItemType extends AbstractItem {
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
*/
gcChildren (transaction, store) {
gcChildren (store) {
let item = this.type._start
while (item !== null) {
item.gc(transaction, store, true)
item.gc(store, true)
item = item.right
}
this.type._start = null
this.type._map.forEach(item => {
while (item !== null) {
item.gc(transaction, store, true)
item.gc(store, true)
// @ts-ignore
item = item.left
}
@@ -150,13 +149,12 @@ export class ItemType extends AbstractItem {
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*/
gc (transaction, store, parentGCd) {
this.gcChildren(transaction, store)
super.gc(transaction, store, parentGCd)
gc (store, parentGCd) {
this.gcChildren(store)
super.gc(store, parentGCd)
}
}

View File

@@ -11,7 +11,7 @@ import {
ItemBinary,
createID,
getItemCleanStart,
Y, Snapshot, Transaction, EventHandler, YEvent, AbstractItem, // eslint-disable-line
Doc, Snapshot, Transaction, EventHandler, YEvent, AbstractItem, // eslint-disable-line
} from '../internals.js'
import * as map from 'lib0/map.js'
@@ -64,9 +64,9 @@ export class AbstractType {
this._start = null
/**
* @private
* @type {Y|null}
* @type {Doc|null}
*/
this._y = null
this.doc = null
this._length = 0
/**
* Event handlers
@@ -87,12 +87,12 @@ export class AbstractType {
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
* @param {Doc} y The Yjs instance
* @param {ItemType|null} item
* @private
*/
_integrate (y, item) {
this._y = y
this.doc = y
this._item = item
}
@@ -182,7 +182,7 @@ export class AbstractType {
* @private
* @function
*/
export const typeArrayToArray = type => {
export const typeListToArray = type => {
const cs = []
let n = type._start
while (n !== null) {
@@ -205,7 +205,7 @@ export const typeArrayToArray = type => {
* @private
* @function
*/
export const typeArrayToArraySnapshot = (type, snapshot) => {
export const typeListToArraySnapshot = (type, snapshot) => {
const cs = []
let n = type._start
while (n !== null) {
@@ -229,7 +229,7 @@ export const typeArrayToArraySnapshot = (type, snapshot) => {
* @private
* @function
*/
export const typeArrayForEach = (type, f) => {
export const typeListForEach = (type, f) => {
let index = 0
let n = type._start
while (n !== null) {
@@ -252,12 +252,12 @@ export const typeArrayForEach = (type, f) => {
* @private
* @function
*/
export const typeArrayMap = (type, f) => {
export const typeListMap = (type, f) => {
/**
* @type {Array<any>}
*/
const result = []
typeArrayForEach(type, (c, i) => {
typeListForEach(type, (c, i) => {
result.push(f(c, i, type))
})
return result
@@ -270,7 +270,7 @@ export const typeArrayMap = (type, f) => {
* @private
* @function
*/
export const typeArrayCreateIterator = type => {
export const typeListCreateIterator = type => {
let n = type._start
/**
* @type {Array<any>|null}
@@ -326,7 +326,7 @@ export const typeArrayCreateIterator = type => {
* @private
* @function
*/
export const typeArrayForEachSnapshot = (type, f, snapshot) => {
export const typeListForEachSnapshot = (type, f, snapshot) => {
let index = 0
let n = type._start
while (n !== null) {
@@ -348,7 +348,7 @@ export const typeArrayForEachSnapshot = (type, f, snapshot) => {
* @private
* @function
*/
export const typeArrayGet = (type, index) => {
export const typeListGet = (type, index) => {
for (let n = type._start; n !== null; n = n.right) {
if (!n.deleted && n.countable) {
if (index < n.length) {
@@ -363,12 +363,12 @@ export const typeArrayGet = (type, index) => {
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {AbstractItem?} referenceItem
* @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content
* @param {Array<Object<string,any>|Array<any>|number|string|Uint8Array>} content
*
* @private
* @function
*/
export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
let left = referenceItem
const right = referenceItem === null ? parent._start : referenceItem.right
/**
@@ -393,10 +393,9 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem,
default:
packJsonContent()
switch (c.constructor) {
case Uint8Array:
case ArrayBuffer:
// @ts-ignore c is definitely an ArrayBuffer
left = new ItemBinary(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, c)
// @ts-ignore
left = new ItemBinary(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new Uint8Array(/** @type {Uint8Array} */ (c)))
left.integrate(transaction)
break
default:
@@ -416,14 +415,14 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem,
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {number} index
* @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content
* @param {Array<Object<string,any>|Array<any>|number|string|Uint8Array>} content
*
* @private
* @function
*/
export const typeArrayInsertGenerics = (transaction, parent, index, content) => {
export const typeListInsertGenerics = (transaction, parent, index, content) => {
if (index === 0) {
return typeArrayInsertGenericsAfter(transaction, parent, null, content)
return typeListInsertGenericsAfter(transaction, parent, null, content)
}
let n = parent._start
for (; n !== null; n = n.right) {
@@ -431,14 +430,14 @@ export const typeArrayInsertGenerics = (transaction, parent, index, content) =>
if (index <= n.length) {
if (index < n.length) {
// insert in-between
getItemCleanStart(transaction, transaction.y.store, createID(n.id.client, n.id.clock + index))
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + index))
}
break
}
index -= n.length
}
}
return typeArrayInsertGenericsAfter(transaction, parent, n, content)
return typeListInsertGenericsAfter(transaction, parent, n, content)
}
/**
@@ -450,14 +449,14 @@ export const typeArrayInsertGenerics = (transaction, parent, index, content) =>
* @private
* @function
*/
export const typeArrayDelete = (transaction, parent, index, length) => {
export const typeListDelete = (transaction, parent, index, length) => {
if (length === 0) { return }
let n = parent._start
// compute the first item to be deleted
for (; n !== null && index > 0; n = n.right) {
if (!n.deleted && n.countable) {
if (index < n.length) {
getItemCleanStart(transaction, transaction.y.store, createID(n.id.client, n.id.clock + index))
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + index))
}
index -= n.length
}
@@ -466,7 +465,7 @@ export const typeArrayDelete = (transaction, parent, index, length) => {
while (length > 0 && n !== null) {
if (!n.deleted) {
if (length < n.length) {
getItemCleanStart(transaction, transaction.y.store, createID(n.id.client, n.id.clock + length))
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + length))
}
n.delete(transaction)
length -= n.length
@@ -497,7 +496,7 @@ export const typeMapDelete = (transaction, parent, key) => {
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {string} key
* @param {Object|number|Array<any>|string|ArrayBuffer|AbstractType<any>} value
* @param {Object|number|Array<any>|string|Uint8Array|AbstractType<any>} value
*
* @private
* @function
@@ -515,7 +514,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
case String:
new ItemJSON(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, [value]).integrate(transaction)
break
case ArrayBuffer:
case Uint8Array:
new ItemBinary(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, value).integrate(transaction)
break
default:
@@ -530,7 +529,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
/**
* @param {AbstractType<any>} parent
* @param {string} key
* @return {Object<string,any>|number|Array<any>|string|ArrayBuffer|AbstractType<any>|undefined}
* @return {Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
*
* @private
* @function
@@ -542,7 +541,7 @@ export const typeMapGet = (parent, key) => {
/**
* @param {AbstractType<any>} parent
* @return {Object<string,Object<string,any>|number|Array<any>|string|ArrayBuffer|AbstractType<any>|undefined>}
* @return {Object<string,Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
*
* @private
* @function
@@ -577,7 +576,7 @@ export const typeMapHas = (parent, key) => {
* @param {AbstractType<any>} parent
* @param {string} key
* @param {Snapshot} snapshot
* @return {Object<string,any>|number|Array<any>|string|ArrayBuffer|AbstractType<any>|undefined}
* @return {Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
*
* @private
* @function

View File

@@ -5,17 +5,17 @@
import {
YEvent,
AbstractType,
typeArrayGet,
typeArrayToArray,
typeArrayForEach,
typeArrayCreateIterator,
typeArrayInsertGenerics,
typeArrayDelete,
typeArrayMap,
typeListGet,
typeListToArray,
typeListForEach,
typeListCreateIterator,
typeListInsertGenerics,
typeListDelete,
typeListMap,
YArrayRefID,
callTypeObservers,
transact,
Y, Transaction, ItemType, // eslint-disable-line
Doc, Transaction, ItemType, // eslint-disable-line
} from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
@@ -58,7 +58,7 @@ export class YArray extends AbstractType {
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
* @param {Doc} y The Yjs instance
* @param {ItemType} item
*
* @private
@@ -101,9 +101,9 @@ export class YArray extends AbstractType {
* @param {Array<T>} content The array of content
*/
insert (index, content) {
if (this._y !== null) {
transact(this._y, transaction => {
typeArrayInsertGenerics(transaction, this, index, content)
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListInsertGenerics(transaction, this, index, content)
})
} else {
// @ts-ignore _prelimContent is defined because this is not yet integrated
@@ -127,9 +127,9 @@ export class YArray extends AbstractType {
* @param {number} length The number of elements to remove. Defaults to 1.
*/
delete (index, length = 1) {
if (this._y !== null) {
transact(this._y, transaction => {
typeArrayDelete(transaction, this, index, length)
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListDelete(transaction, this, index, length)
})
} else {
// @ts-ignore _prelimContent is defined because this is not yet integrated
@@ -144,7 +144,7 @@ export class YArray extends AbstractType {
* @return {T}
*/
get (index) {
return typeArrayGet(this, index)
return typeListGet(this, index)
}
/**
@@ -153,7 +153,7 @@ export class YArray extends AbstractType {
* @return {Array<T>}
*/
toArray () {
return typeArrayToArray(this)
return typeListToArray(this)
}
/**
@@ -176,7 +176,7 @@ export class YArray extends AbstractType {
*/
map (f) {
// @ts-ignore
return typeArrayMap(this, f)
return typeListMap(this, f)
}
/**
@@ -185,14 +185,14 @@ export class YArray extends AbstractType {
* @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray.
*/
forEach (f) {
typeArrayForEach(this, f)
typeListForEach(this, f)
}
/**
* @return {IterableIterator<T>}
*/
[Symbol.iterator] () {
return typeArrayCreateIterator(this)
return typeListCreateIterator(this)
}
/**

View File

@@ -14,7 +14,7 @@ import {
YMapRefID,
callTypeObservers,
transact,
Y, Transaction, ItemType, // eslint-disable-line
Doc, Transaction, ItemType, // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -38,7 +38,7 @@ export class YMapEvent extends YEvent {
}
/**
* @template T number|string|Object|Array|ArrayBuffer
* @template T number|string|Object|Array|Uint8Array
* A shared Map implementation.
*
* @extends AbstractType<YMapEvent<T>>
@@ -60,7 +60,7 @@ export class YMap extends AbstractType {
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
* @param {Doc} y The Yjs instance
* @param {ItemType} item
*
* @private
@@ -144,8 +144,8 @@ export class YMap extends AbstractType {
* @param {string} key The key of the element to remove.
*/
delete (key) {
if (this._y !== null) {
transact(this._y, transaction => {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapDelete(transaction, this, key)
})
} else {
@@ -161,8 +161,8 @@ export class YMap extends AbstractType {
* @param {T} value The value of the element to add
*/
set (key, value) {
if (this._y !== null) {
transact(this._y, transaction => {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapSet(transaction, this, key, value)
})
} else {

View File

@@ -16,7 +16,7 @@ import {
YTextRefID,
callTypeObservers,
transact,
Y, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line
Doc, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line
} from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
@@ -303,7 +303,7 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
case ItemEmbed:
case ItemString:
if (length < right.length) {
getItemCleanStart(transaction, transaction.y.store, createID(right.id.client, right.id.clock + length))
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
}
length -= right.length
break
@@ -337,7 +337,7 @@ const deleteText = (transaction, left, right, currentAttributes, length) => {
case ItemEmbed:
case ItemString:
if (length < right.length) {
getItemCleanStart(transaction, transaction.y.store, createID(right.id.client, right.id.clock + length))
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
}
length -= right.length
right.delete(transaction)
@@ -411,7 +411,7 @@ class YTextEvent extends YEvent {
*/
get delta () {
if (this._delta === null) {
const y = this.target._y
const y = this.target.doc
// @ts-ignore
transact(y, transaction => {
/**
@@ -629,7 +629,7 @@ export class YText extends AbstractType {
}
/**
* @param {Y} y
* @param {Doc} y
* @param {ItemType} item
*
* @private
@@ -686,8 +686,8 @@ export class YText extends AbstractType {
* @public
*/
applyDelta (delta) {
if (this._y !== null) {
transact(this._y, transaction => {
if (this.doc !== null) {
transact(this.doc, transaction => {
/**
* @type {ItemListPosition}
*/
@@ -803,7 +803,7 @@ export class YText extends AbstractType {
if (text.length <= 0) {
return
}
const y = this._y
const y = this.doc
if (y !== null) {
transact(y, transaction => {
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
@@ -829,7 +829,7 @@ export class YText extends AbstractType {
if (embed.constructor !== Object) {
throw new Error('Embed must be an Object')
}
const y = this._y
const y = this.doc
if (y !== null) {
transact(y, transaction => {
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
@@ -853,7 +853,7 @@ export class YText extends AbstractType {
if (length === 0) {
return
}
const y = this._y
const y = this.doc
if (y !== null) {
transact(y, transaction => {
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
@@ -876,7 +876,7 @@ export class YText extends AbstractType {
* @public
*/
format (index, length, attributes) {
const y = this._y
const y = this.doc
if (y !== null) {
transact(y, transaction => {
let { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)

View File

@@ -6,9 +6,9 @@ import {
typeMapSet,
typeMapGet,
typeMapGetAll,
typeArrayForEach,
typeListForEach,
YXmlElementRefID,
Snapshot, Y, ItemType // eslint-disable-line
Snapshot, Doc, ItemType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -39,7 +39,7 @@ export class YXmlElement extends YXmlFragment {
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
* @param {Doc} y The Yjs instance
* @param {ItemType} item
* @private
*/
@@ -100,8 +100,8 @@ export class YXmlElement extends YXmlFragment {
* @public
*/
removeAttribute (attributeName) {
if (this._y !== null) {
transact(this._y, transaction => {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapDelete(transaction, this, attributeName)
})
} else {
@@ -119,8 +119,8 @@ export class YXmlElement extends YXmlFragment {
* @public
*/
setAttribute (attributeName, attributeValue) {
if (this._y !== null) {
transact(this._y, transaction => {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapSet(transaction, this, attributeName, attributeValue)
})
} else {
@@ -176,7 +176,7 @@ export class YXmlElement extends YXmlFragment {
for (let key in attrs) {
dom.setAttribute(key, attrs[key])
}
typeArrayForEach(this, yxml => {
typeListForEach(this, yxml => {
dom.appendChild(yxml.toDOM(_document, hooks, binding))
})
if (binding !== undefined) {

View File

@@ -6,11 +6,11 @@ import {
YXmlEvent,
YXmlElement,
AbstractType,
typeArrayMap,
typeArrayForEach,
typeArrayInsertGenerics,
typeArrayDelete,
typeArrayToArray,
typeListMap,
typeListForEach,
typeListInsertGenerics,
typeListDelete,
typeListToArray,
YXmlFragmentRefID,
callTypeObservers,
transact,
@@ -211,7 +211,7 @@ export class YXmlFragment extends AbstractType {
* @return {string} The string representation of all children.
*/
toString () {
return typeArrayMap(this, xml => xml.toString()).join('')
return typeListMap(this, xml => xml.toString()).join('')
}
toJSON () {
@@ -238,7 +238,7 @@ export class YXmlFragment extends AbstractType {
if (binding !== undefined) {
binding._createAssociation(fragment, this)
}
typeArrayForEach(this, xmlType => {
typeListForEach(this, xmlType => {
fragment.insertBefore(xmlType.toDOM(_document, hooks, binding), null)
})
return fragment
@@ -255,9 +255,9 @@ export class YXmlFragment extends AbstractType {
* @param {Array<YXmlElement|YXmlText>} content The array of content
*/
insert (index, content) {
if (this._y !== null) {
transact(this._y, transaction => {
typeArrayInsertGenerics(transaction, this, index, content)
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListInsertGenerics(transaction, this, index, content)
})
} else {
// @ts-ignore _prelimContent is defined because this is not yet integrated
@@ -272,9 +272,9 @@ export class YXmlFragment extends AbstractType {
* @param {number} [length=1] The number of elements to remove. Defaults to 1.
*/
delete (index, length = 1) {
if (this._y !== null) {
transact(this._y, transaction => {
typeArrayDelete(transaction, this, index, length)
if (this.doc !== null) {
transact(this.doc, transaction => {
typeListDelete(transaction, this, index, length)
})
} else {
// @ts-ignore _prelimContent is defined because this is not yet integrated
@@ -287,7 +287,7 @@ export class YXmlFragment extends AbstractType {
* @return {Array<YXmlElement|YXmlText|YXmlHook>}
*/
toArray () {
return typeArrayToArray(this)
return typeListToArray(this)
}
/**
* Transform the properties of this type to binary and write it to an

View File

@@ -48,7 +48,7 @@ export class DeleteSet {
/**
* Iterate over all structs that were deleted.
*
* This function expects that the deletes structs are not deleted. Hence, you can
* This function expects that the deletes structs are not merged. Hence, you can
* probably only use it in type observes and `afterTransaction` events. But not
* in `afterTransactionCleanup`.
*
@@ -266,6 +266,6 @@ export const readDeleteSet = (decoder, transaction, store) => {
if (unappliedDS.clients.size > 0) {
const unappliedDSEncoder = encoding.createEncoder()
writeDeleteSet(unappliedDSEncoder, unappliedDS)
store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toBuffer(unappliedDSEncoder)))
store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
}
}

View File

@@ -10,26 +10,23 @@ import {
YMap,
YXmlFragment,
transact,
Transaction, YEvent // eslint-disable-line
AbstractItem, Transaction, YEvent // eslint-disable-line
} from '../internals.js'
import { Observable } from 'lib0/observable.js'
import * as random from 'lib0/random.js'
import * as map from 'lib0/map.js'
// @todo rename to shared document
/**
* A Yjs instance handles the state of shared data.
* @extends Observable<string>
*/
export class Y extends Observable {
export class Doc extends Observable {
/**
* @param {Object|undefined} conf configuration
*/
constructor (conf = {}) {
super()
// todo: change to clientId
this.clientID = random.uint32()
/**
* @type {Map<string, AbstractType<YEvent>>}
@@ -82,7 +79,7 @@ export class Y extends Observable {
* }
*
* @param {string} name
* @param {Function} TypeConstructor The constructor of the type definition
* @param {Function} TypeConstructor The constructor of the type definition. E.g. Y.Text, Y.Array, Y.Map, ...
* @return {AbstractType<any>} The created type. Constructed with TypeConstructor
*
* @public
@@ -97,9 +94,18 @@ export class Y extends Observable {
const Constr = type.constructor
if (TypeConstructor !== AbstractType && Constr !== TypeConstructor) {
if (Constr === AbstractType) {
const t = new Constr()
// @ts-ignore
const t = new TypeConstructor()
t._map = type._map
type._map.forEach(/** @param {AbstractItem?} n */ n => {
for (; n !== null; n = n.left) {
n.parent = t
}
})
t._start = type._start
for (let n = t._start; n !== null; n = n.right) {
n.parent = t
}
t._length = type._length
this.share.set(name, t)
t._integrate(this, null)

View File

@@ -22,16 +22,6 @@ export class ID {
*/
this.clock = clock
}
/**
* @deprecated
* @todo remove and adapt relative position implementation
*/
toJSON () {
return {
client: this.client,
clock: this.clock
}
}
}
/**
@@ -91,7 +81,7 @@ export const readID = decoder =>
*/
export const findRootTypeKey = type => {
// @ts-ignore _y must be defined, otherwise unexpected case
for (let [key, value] of type._y.share) {
for (let [key, value] of type.doc.share) {
if (value === type) {
return key
}

View File

@@ -1,6 +1,3 @@
/**
* @module Cursors
*/
import {
getItem,
@@ -13,7 +10,7 @@ import {
findRootTypeKey,
AbstractItem,
ItemType,
ID, StructStore, Y, AbstractType // eslint-disable-line
ID, StructStore, Doc, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -21,29 +18,30 @@ import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js'
/**
* A Cursor is a relative position that is based on the Yjs model. In contrast to an
* absolute position (position by index), the Cursor can be
* recomputed when remote changes are received. For example:
* A relative position is based on the Yjs model and is not affected by document changes.
* E.g. If you place a relative position before a certain character, it will always point to this character.
* If you place a relative position at the end of a type, it will always point to the end of the type.
*
* ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the cursor position.
* A numeric position is often unsuited for user selections, because it does not change when content is inserted
* before or after.
*
* A relative cursor position can be obtained with the function
* ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position.
*
* One of the properties must be defined.
*
* @example
* // Current cursor position is at position 10
* const relativePosition = createCursorFromOffset(yText, 10)
* const relativePosition = createRelativePositionFromIndex(yText, 10)
* // modify yText
* yText.insert(0, 'abc')
* yText.delete(3, 10)
* // Compute the cursor position
* const absolutePosition = toAbsolutePosition(y, relativePosition)
* const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition)
* absolutePosition.type === yText // => true
* console.log('cursor location is ' + absolutePosition.offset) // => cursor location is 3
* console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3
*
*/
export class Cursor {
export class RelativePosition {
/**
* @param {ID|null} type
* @param {string|null} tname
@@ -63,35 +61,22 @@ export class Cursor {
*/
this.item = item
}
toJSON () {
const json = {}
if (this.type !== null) {
json.type = this.type.toJSON()
}
if (this.tname !== null) {
json.tname = this.tname
}
if (this.item !== null) {
json.item = this.item.toJSON()
}
return json
}
}
/**
* @param {Object} json
* @return {Cursor}
* @return {RelativePosition}
*
* @function
*/
export const createCursorFromJSON = json => new Cursor(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))
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))
export class AbsolutePosition {
/**
* @param {AbstractType<any>} type
* @param {number} offset
* @param {number} index
*/
constructor (type, offset) {
constructor (type, index) {
/**
* @type {AbstractType<any>}
*/
@@ -99,17 +84,17 @@ export class AbsolutePosition {
/**
* @type {number}
*/
this.offset = offset
this.index = index
}
}
/**
* @param {AbstractType<any>} type
* @param {number} offset
* @param {number} index
*
* @function
*/
export const createAbsolutePosition = (type, offset) => new AbsolutePosition(type, offset)
export const createAbsolutePosition = (type, index) => new AbsolutePosition(type, index)
/**
* @param {AbstractType<any>} type
@@ -117,7 +102,7 @@ export const createAbsolutePosition = (type, offset) => new AbsolutePosition(typ
*
* @function
*/
export const createCursor = (type, item) => {
export const createRelativePosition = (type, item) => {
let typeid = null
let tname = null
if (type._item === null) {
@@ -125,40 +110,40 @@ export const createCursor = (type, item) => {
} else {
typeid = type._item.id
}
return new Cursor(typeid, tname, item)
return new RelativePosition(typeid, tname, item)
}
/**
* Create a relativePosition based on a absolute position.
*
* @param {AbstractType<any>} type The base type (e.g. YText or YArray).
* @param {number} offset The absolute position.
* @return {Cursor}
* @param {number} index The absolute position.
* @return {RelativePosition}
*
* @function
*/
export const createCursorFromTypeOffset = (type, offset) => {
export const createRelativePositionFromTypeIndex = (type, index) => {
let t = type._start
while (t !== null) {
if (!t.deleted && t.countable) {
if (t.length > offset) {
if (t.length > index) {
// case 1: found position somewhere in the linked list
return createCursor(type, createID(t.id.client, t.id.clock + offset))
return createRelativePosition(type, createID(t.id.client, t.id.clock + index))
}
offset -= t.length
index -= t.length
}
t = t.right
}
return createCursor(type, null)
return createRelativePosition(type, null)
}
/**
* @param {encoding.Encoder} encoder
* @param {Cursor} rpos
* @param {RelativePosition} rpos
*
* @function
*/
export const writeCursor = (encoder, rpos) => {
export const writeRelativePosition = (encoder, rpos) => {
const { type, tname, item } = rpos
if (item !== null) {
encoding.writeVarUint(encoder, 0)
@@ -177,15 +162,23 @@ export const writeCursor = (encoder, rpos) => {
return encoder
}
/**
* @param {RelativePosition} rpos
* @return {Uint8Array}
*/
export const encodeRelativePosition = rpos => {
const encoder = encoding.createEncoder()
writeRelativePosition(encoder, rpos)
return encoding.toUint8Array(encoder)
}
/**
* @param {decoding.Decoder} decoder
* @param {Y} y
* @param {StructStore} store
* @return {Cursor|null}
* @return {RelativePosition|null}
*
* @function
*/
export const readCursor = (decoder, y, store) => {
export const readRelativePosition = decoder => {
let type = null
let tname = null
let itemID = null
@@ -203,23 +196,29 @@ export const readCursor = (decoder, y, store) => {
type = readID(decoder)
}
}
return new Cursor(type, tname, itemID)
return new RelativePosition(type, tname, itemID)
}
/**
* @param {Cursor} cursor
* @param {Y} y
* @param {Uint8Array} uint8Array
* @return {RelativePosition|null}
*/
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
/**
* @param {RelativePosition} rpos
* @param {Doc} doc
* @return {AbsolutePosition|null}
*
* @function
*/
export const createAbsolutePositionFromCursor = (cursor, y) => {
const store = y.store
const rightID = cursor.item
const typeID = cursor.type
const tname = cursor.tname
export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
const store = doc.store
const rightID = rpos.item
const typeID = rpos.type
const tname = rpos.tname
let type = null
let offset = 0
let index = 0
if (rightID !== null) {
if (getState(store, rightID.client) <= rightID.clock) {
return null
@@ -228,18 +227,18 @@ export const createAbsolutePositionFromCursor = (cursor, y) => {
if (!(right instanceof AbstractItem)) {
return null
}
offset = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock
index = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock
let n = right.left
while (n !== null) {
if (!n.deleted && n.countable) {
offset += n.length
index += n.length
}
n = n.left
}
type = right.parent
} else {
if (tname !== null) {
type = y.get(tname)
type = doc.get(tname)
} else if (typeID !== null) {
if (getState(store, typeID.client) <= typeID.clock) {
// type does not exist yet
@@ -255,20 +254,20 @@ export const createAbsolutePositionFromCursor = (cursor, y) => {
} else {
throw error.unexpectedCase()
}
offset = type._length
index = type._length
}
if (type._item !== null && type._item.deleted) {
return null
}
return createAbsolutePosition(type, offset)
return createAbsolutePosition(type, index)
}
/**
* @param {Cursor|null} a
* @param {Cursor|null} b
* @param {RelativePosition|null} a
* @param {RelativePosition|null} b
*
* @function
*/
export const compareCursors = (a, b) => a === b || (
export const compareRelativePositions = (a, b) => a === b || (
a !== null && b !== null && a.tname === b.tname && compareIDs(a.item, b.item) && compareIDs(a.type, b.type)
)

View File

@@ -6,12 +6,10 @@ import {
export class Snapshot {
/**
* @param {DeleteSet} ds delete store
* @param {DeleteSet} ds
* @param {Map<number,number>} sm state map
* @param {Map<number,string>} userMap
* @private
*/
constructor (ds, sm, userMap) {
constructor (ds, sm) {
/**
* @type {DeleteSet}
* @private
@@ -23,14 +21,15 @@ export class Snapshot {
* @private
*/
this.sm = sm
/**
* @type {Map<number,string>}
* @private
*/
this.userMap = userMap
}
}
/**
* @param {DeleteSet} ds
* @param {Map<number,number>} sm
*/
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
/**
* @param {AbstractItem} item
* @param {Snapshot|undefined} snapshot

View File

@@ -6,8 +6,7 @@ import {
import * as math from 'lib0/math.js'
import * as error from 'lib0/error.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
export class StructStore {
constructor () {
@@ -51,7 +50,7 @@ export class StructStore {
* @public
* @function
*/
export const getStates = store => {
export const getStateVector = store => {
const sm = new Map()
store.clients.forEach((structs, client) => {
const struct = structs[structs.length - 1]
@@ -262,42 +261,3 @@ export const replaceStruct = (store, struct, newStruct) => {
const structs = store.clients.get(struct.id.client)
structs[findIndexSS(structs, struct.id.clock)] = newStruct
}
/**
* Read StateMap from Decoder and return as Map
*
* @param {decoding.Decoder} decoder
* @return {Map<number,number>}
*
* @private
* @function
*/
export const readStatesAsMap = decoder => {
const ss = new Map()
const ssLength = decoding.readVarUint(decoder)
for (let i = 0; i < ssLength; i++) {
const client = decoding.readVarUint(decoder)
const clock = decoding.readVarUint(decoder)
ss.set(client, clock)
}
return ss
}
/**
* Write StateMap to Encoder
*
* @param {encoding.Encoder} encoder
* @param {StructStore} store
*
* @private
* @function
*/
export const writeStates = (encoder, store) => {
encoding.writeVarUint(encoder, store.clients.size)
store.clients.forEach((structs, client) => {
const id = structs[structs.length - 1].id
encoding.writeVarUint(encoder, id.client)
encoding.writeVarUint(encoder, id.clock)
})
return encoder
}

View File

@@ -6,11 +6,11 @@ import {
writeDeleteSet,
DeleteSet,
sortAndMergeDeleteSet,
getStates,
getStateVector,
findIndexSS,
callEventHandlerListeners,
AbstractItem,
ID, AbstractType, AbstractStruct, YEvent, Y // eslint-disable-line
ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -43,15 +43,15 @@ import * as math from 'lib0/math.js'
*/
export class Transaction {
/**
* @param {Y} y
* @param {Doc} doc
* @param {any} origin
*/
constructor (y, origin) {
constructor (doc, origin) {
/**
* The Yjs instance.
* @type {Y}
* @type {Doc}
*/
this.y = y
this.doc = doc
/**
* Describes the set of deleted items by ids
* @type {DeleteSet}
@@ -61,7 +61,7 @@ export class Transaction {
* Holds the state before the transaction started.
* @type {Map<Number,Number>}
*/
this.beforeState = getStates(y.store)
this.beforeState = getStateVector(doc.store)
/**
* Holds the state after the transaction.
* @type {Map<Number,Number>}
@@ -80,11 +80,6 @@ export class Transaction {
* @type {Map<AbstractType<YEvent>,Array<YEvent>>}
*/
this.changedParentTypes = new Map()
/**
* @type {encoding.Encoder|null}
* @private
*/
this._updateMessage = null
/**
* @type {Set<ID>}
* @private
@@ -95,21 +90,20 @@ export class Transaction {
*/
this.origin = origin
}
/**
* @type {encoding.Encoder|null}
* @public
*/
get updateMessage () {
// only create if content was added in transaction (state or ds changed)
if (this._updateMessage === null && (this.deleteSet.clients.size > 0 || map.any(this.afterState, (clock, client) => this.beforeState.get(client) !== clock))) {
const encoder = encoding.createEncoder()
sortAndMergeDeleteSet(this.deleteSet)
writeStructsFromTransaction(encoder, this)
writeDeleteSet(encoder, this.deleteSet)
this._updateMessage = encoder
}
return this._updateMessage
}
/**
* @param {Transaction} transaction
*/
export const computeUpdateMessageFromTransaction = transaction => {
if (transaction.deleteSet.clients.size === 0 && !map.any(transaction.afterState, (clock, client) => transaction.beforeState.get(client) !== clock)) {
return null
}
const encoder = encoding.createEncoder()
sortAndMergeDeleteSet(transaction.deleteSet)
writeStructsFromTransaction(encoder, transaction)
writeDeleteSet(encoder, transaction.deleteSet)
return encoder
}
/**
@@ -119,45 +113,44 @@ export class Transaction {
* @function
*/
export const nextID = transaction => {
const y = transaction.y
const y = transaction.doc
return createID(y.clientID, getState(y.store, y.clientID))
}
/**
* Implements the functionality of `y.transact(()=>{..})`
*
* @param {Y} y
* @param {Doc} doc
* @param {function(Transaction):void} f
* @param {any} [origin]
*
* @private
* @function
*/
export const transact = (y, f, origin = null) => {
const transactionCleanups = y._transactionCleanups
export const transact = (doc, f, origin = null) => {
const transactionCleanups = doc._transactionCleanups
let initialCall = false
if (y._transaction === null) {
if (doc._transaction === null) {
initialCall = true
y._transaction = new Transaction(y, origin)
transactionCleanups.push(y._transaction)
y.emit('beforeTransaction', [y._transaction, y])
doc._transaction = new Transaction(doc, origin)
transactionCleanups.push(doc._transaction)
doc.emit('beforeTransaction', [doc._transaction, doc])
}
try {
f(y._transaction)
f(doc._transaction)
} finally {
// @todo set after state here
if (initialCall && transactionCleanups[0] === y._transaction) {
if (initialCall && transactionCleanups[0] === doc._transaction) {
// The first transaction ended, now process observer calls.
// Observer call may create new transactions for which we need to call the observers and do cleanup.
// We don't want to nest these calls, so we execute these calls one after another
for (let i = 0; i < transactionCleanups.length; i++) {
const transaction = transactionCleanups[i]
const store = transaction.y.store
const store = transaction.doc.store
const ds = transaction.deleteSet
sortAndMergeDeleteSet(ds)
transaction.afterState = getStates(transaction.y.store)
y._transaction = null
y.emit('beforeObserverCalls', [transaction, y])
transaction.afterState = getStateVector(transaction.doc.store)
doc._transaction = null
doc.emit('beforeObserverCalls', [transaction, doc])
// emit change events on changed types
transaction.changed.forEach((subs, itemtype) => {
itemtype._callObserver(transaction, subs)
@@ -175,7 +168,7 @@ export const transact = (y, f, origin = null) => {
// because we know it has at least one element
callEventHandlerListeners(type._dEH, events, transaction)
})
y.emit('afterTransaction', [transaction, y])
doc.emit('afterTransaction', [transaction, doc])
/**
* @param {Array<AbstractStruct>} structs
* @param {number} pos
@@ -213,7 +206,7 @@ export const transact = (y, f, origin = null) => {
break
}
if (struct.deleted && struct instanceof AbstractItem) {
struct.gc(transaction, store, false)
struct.gc(store, false)
}
}
}
@@ -276,10 +269,15 @@ export const transact = (y, f, origin = null) => {
}
}
// @todo Merge all the transactions into one and provide send the data as a single update message
// @todo implement a dedicatet event that we can use to send updates to other peer
y.emit('afterTransactionCleanup', [transaction, y])
doc.emit('afterTransactionCleanup', [transaction, doc])
if (doc._observers.has('update')) {
const updateMessage = computeUpdateMessageFromTransaction(transaction)
if (updateMessage !== null) {
doc.emit('update', [encoding.toUint8Array(updateMessage), transaction.origin, doc])
}
}
}
y._transactionCleanups = []
doc._transactionCleanups = []
}
}
}

View File

@@ -116,10 +116,10 @@ export class UndoManager {
this._undoing = false
this._redoing = false
this._lastTransactionWasUndo = false
const y = scope._y
this.y = y
const doc = scope.doc
this.y = doc
let bindingInfos
y.on('beforeTransaction', (y, transaction, remote) => {
doc.on('beforeTransaction', (y, transaction, remote) => {
if (!remote) {
// Store binding information before transaction is executed
// By restoring the binding information, we can make sure that the state
@@ -130,7 +130,7 @@ export class UndoManager {
})
}
})
y.on('afterTransaction', (y, transaction, remote) => {
doc.on('afterTransaction', (y, transaction, remote) => {
if (!remote && transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y, transaction, bindingInfos)
if (!this._undoing) {
@@ -200,3 +200,4 @@ export class UndoManager {
return performedRedo
}
}
}

View File

@@ -17,11 +17,11 @@ import {
createID,
readID,
getState,
getStates,
getStateVector,
readDeleteSet,
writeDeleteSet,
createDeleteSetFromStructStore,
Transaction, AbstractStruct, AbstractStructRef, StructStore, ID // eslint-disable-line
Doc, Transaction, AbstractStruct, AbstractStructRef, StructStore, ID // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -104,7 +104,7 @@ export const writeClientsStructs = (encoder, store, _sm) => {
sm.set(client, clock)
}
})
getStates(store).forEach((clock, client) => {
getStateVector(store).forEach((clock, client) => {
if (!_sm.has(client)) {
sm.set(client, 0)
}
@@ -250,7 +250,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => {
* @private
* @function
*/
export const writeStructsFromTransaction = (encoder, transaction) => writeClientsStructs(encoder, transaction.y.store, transaction.beforeState)
export const writeStructsFromTransaction = (encoder, transaction) => writeClientsStructs(encoder, transaction.doc.store, transaction.beforeState)
/**
* @param {StructStore} store
@@ -297,25 +297,127 @@ export const readStructs = (decoder, transaction, store) => {
}
/**
* Read and apply a document update.
*
* This function has the same effect as `applyUpdate` but accepts an decoder.
*
* @param {decoding.Decoder} decoder
* @param {Transaction} transaction
* @param {StructStore} store
* @param {Doc} ydoc
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
*
* @function
*/
export const readModel = (decoder, transaction, store) => {
readStructs(decoder, transaction, store)
readDeleteSet(decoder, transaction, store)
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
ydoc.transact(transaction => {
readStructs(decoder, transaction, ydoc.store)
readDeleteSet(decoder, transaction, ydoc.store)
}, transactionOrigin)
/**
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
*
* This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
*
* @param {Doc} ydoc
* @param {Uint8Array} update
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
*
* @function
*/
export const applyUpdate = (ydoc, update, transactionOrigin) =>
readUpdate(decoding.createDecoder(update), ydoc, transactionOrigin)
/**
* Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will
* only write the operations that are missing.
*
* @param {encoding.Encoder} encoder
* @param {Doc} doc
* @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs
*
* @function
*/
export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) => {
writeClientsStructs(encoder, doc.store, targetStateVector)
writeDeleteSet(encoder, createDeleteSetFromStructStore(doc.store))
}
/**
* @param {encoding.Encoder} encoder
* @param {StructStore} store
* @param {Map<number,number>} [targetState] The state of the target that receives the update. Leave empty to write all known structs
* 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
* only write the operations that are missing.
*
* Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
*
* @param {Doc} doc
* @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
* @return {Uint8Array}
*
* @function
*/
export const writeModel = (encoder, store, targetState = new Map()) => {
writeClientsStructs(encoder, store, targetState)
writeDeleteSet(encoder, createDeleteSetFromStructStore(store))
export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => {
const encoder = encoding.createEncoder()
const targetStateVector = encodedTargetStateVector == null ? new Map() : decodeStateVector(encodedTargetStateVector)
writeStateAsUpdate(encoder, doc, targetStateVector)
return encoding.toUint8Array(encoder)
}
/**
* Read state vector from Decoder and return as Map
*
* @param {decoding.Decoder} decoder
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
*
* @function
*/
export const readStateVector = decoder => {
const ss = new Map()
const ssLength = decoding.readVarUint(decoder)
for (let i = 0; i < ssLength; i++) {
const client = decoding.readVarUint(decoder)
const clock = decoding.readVarUint(decoder)
ss.set(client, clock)
}
return ss
}
/**
* Read decodedState and return State as Map.
*
* @param {Uint8Array} decodedState
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
*
* @function
*/
export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState))
/**
* Write State Vector to `lib0/encoding.js#Encoder`.
*
* @param {encoding.Encoder} encoder
* @param {Doc} doc
*
* @function
*/
export const writeDocumentStateVector = (encoder, doc) => {
encoding.writeVarUint(encoder, doc.store.clients.size)
doc.store.clients.forEach((structs, client) => {
const id = structs[structs.length - 1].id
encoding.writeVarUint(encoder, id.client)
encoding.writeVarUint(encoder, id.clock)
})
return encoder
}
/**
* Encode State as Uint8Array.
*
* @param {Doc} doc
* @return {Uint8Array}
*
* @function
*/
export const encodeDocumentStateVector = doc => {
const encoder = encoding.createEncoder()
writeDocumentStateVector(encoder, doc)
return encoding.toUint8Array(encoder)
}