Use generic Item with typed content to reduce cache misses

This commit is contained in:
Kevin Jahns 2019-05-28 14:18:20 +02:00
parent 3fba4f25a5
commit 2192aa5821
42 changed files with 1958 additions and 3007 deletions

2033
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"lint": "standard && tsc", "lint": "standard && tsc",
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true", "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true",
"serve-docs": "npm run docs && serve ./docs/", "serve-docs": "npm run docs && serve ./docs/",
"preversion": "PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.js --repitition-time 1000", "preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.js --repitition-time 1000",
"postversion": "git push && git push --tags", "postversion": "git push && git push --tags",
"debug": "concurrently 'live-server --port=3443 --entry-file=test.html' 'npm run watch'", "debug": "concurrently 'live-server --port=3443 --entry-file=test.html' 'npm run watch'",
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.js", "trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.js",
@ -59,9 +59,7 @@
"live-server": "^1.2.1", "live-server": "^1.2.1",
"rollup": "^1.11.3", "rollup": "^1.11.3",
"rollup-cli": "^1.0.9", "rollup-cli": "^1.0.9",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.4", "rollup-plugin-node-resolve": "^4.2.4",
"rollup-plugin-terser": "^4.0.4",
"standard": "^11.0.1", "standard": "^11.0.1",
"tui-jsdoc-template": "^1.2.2", "tui-jsdoc-template": "^1.2.2",
"typescript": "^3.4.5", "typescript": "^3.4.5",

View File

@ -1,6 +1,6 @@
import nodeResolve from 'rollup-plugin-node-resolve' import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import { terser } from 'rollup-plugin-terser' const localImports = process.env.LOCALIMPORTS
const customModules = new Set([ const customModules = new Set([
'y-websocket', 'y-websocket',
@ -23,33 +23,18 @@ const debugResolve = {
if (importee === 'yjs') { if (importee === 'yjs') {
return `${process.cwd()}/src/index.js` return `${process.cwd()}/src/index.js`
} }
/* if (localImports) {
if (customModules.has(importee.split('/')[0])) { if (customModules.has(importee.split('/')[0])) {
return `${process.cwd()}/../${importee}/src/${importee}.js` return `${process.cwd()}/../${importee}/src/${importee}.js`
} }
if (customLibModules.has(importee.split('/')[0])) { if (customLibModules.has(importee.split('/')[0])) {
return `${process.cwd()}/../${importee}` return `${process.cwd()}/../${importee}`
} }
*/ }
return null return null
} }
} }
const minificationPlugins = process.env.PRODUCTION ? [terser({
module: true,
compress: {
hoist_vars: true,
module: true,
passes: 5,
pure_getters: true,
unsafe_comps: true,
unsafe_undefined: true
},
mangle: {
toplevel: true
}
})] : []
export default [{ export default [{
input: './src/index.js', input: './src/index.js',
output: [{ output: [{
@ -84,6 +69,5 @@ export default [{
sourcemap: true, sourcemap: true,
mainFields: ['module', 'browser', 'main'] mainFields: ['module', 'browser', 'main']
}) })
// commonjs()
] ]
}] }]

View File

@ -13,16 +13,16 @@ export {
YMapEvent, YMapEvent,
YArrayEvent, YArrayEvent,
YEvent, YEvent,
AbstractItem, Item,
AbstractStruct, AbstractStruct,
GC, GC,
ItemBinary, ContentBinary,
ItemDeleted, ContentDeleted,
ItemEmbed, ContentEmbed,
ItemFormat, ContentFormat,
ItemJSON, ContentJSON,
ItemString, ContentString,
ItemType, ContentType,
AbstractType, AbstractType,
RelativePosition, RelativePosition,
createRelativePositionFromTypeIndex, createRelativePositionFromTypeIndex,

View File

@ -21,14 +21,14 @@ export * from './types/YXmlHook.js'
export * from './types/YXmlText.js' export * from './types/YXmlText.js'
export * from './structs/AbstractStruct.js' export * from './structs/AbstractStruct.js'
export * from './structs/AbstractItem.js'
export * from './structs/GC.js' export * from './structs/GC.js'
export * from './structs/ItemBinary.js' export * from './structs/ContentBinary.js'
export * from './structs/ItemDeleted.js' export * from './structs/ContentDeleted.js'
export * from './structs/ItemEmbed.js' export * from './structs/ContentEmbed.js'
export * from './structs/ItemFormat.js' export * from './structs/ContentFormat.js'
export * from './structs/ItemJSON.js' export * from './structs/ContentJSON.js'
export * from './structs/ItemString.js' export * from './structs/ContentString.js'
export * from './structs/ItemType.js' export * from './structs/ContentType.js'
export * from './structs/Item.js'
export * from './utils/encoding.js' export * from './utils/encoding.js'

View File

@ -12,14 +12,16 @@ import * as error from 'lib0/error.js'
export class AbstractStruct { export class AbstractStruct {
/** /**
* @param {ID} id * @param {ID} id
* @param {number} length
*/ */
constructor (id) { constructor (id, length) {
/** /**
* The uniqe identifier of this struct. * The uniqe identifier of this struct.
* @type {ID} * @type {ID}
* @readonly * @readonly
*/ */
this.id = id this.id = id
this.length = length
this.deleted = false this.deleted = false
} }
/** /**
@ -32,12 +34,6 @@ export class AbstractStruct {
mergeWith (right) { mergeWith (right) {
return false return false
} }
/**
* @type {number}
*/
get length () {
throw error.methodUnimplemented()
}
/** /**
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {encoding.Encoder} encoder The encoder to write data to.
* @param {number} offset * @param {number} offset
@ -89,10 +85,4 @@ export class AbstractStructRef {
toStruct (transaction, store, offset) { toStruct (transaction, store, offset) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/**
* @type {number}
*/
get length () {
return 1
}
} }

View File

@ -0,0 +1,92 @@
import {
StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as buffer from 'lib0/buffer.js'
import * as error from 'lib0/error.js'
/**
* @private
*/
export class ContentBinary {
/**
* @param {Uint8Array} content
*/
constructor (content) {
this.content = content
}
/**
* @return {number}
*/
getLength () {
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
return [this.content]
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentBinary}
*/
copy () {
return new ContentBinary(this.content)
}
/**
* @param {number} offset
* @return {ContentBinary}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {ContentBinary} right
* @return {boolean}
*/
mergeWith (right) {
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
encoding.writeVarUint8Array(encoder, this.content)
}
/**
* @return {number}
*/
getRef () {
return 3
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentBinary}
*/
export const readContentBinary = decoder => new ContentBinary(buffer.copyUint8Array(decoding.readVarUint8Array(decoder)))

View File

@ -0,0 +1,98 @@
import {
addToDeleteSet,
StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export class ContentDeleted {
/**
* @param {number} len
*/
constructor (len) {
this.len = len
}
/**
* @return {number}
*/
getLength () {
return this.len
}
/**
* @return {Array<any>}
*/
getContent () {
return []
}
/**
* @return {boolean}
*/
isCountable () {
return false
}
/**
* @return {ContentDeleted}
*/
copy () {
return new ContentDeleted(this.len)
}
/**
* @param {number} offset
* @return {ContentDeleted}
*/
splice (offset) {
const right = new ContentDeleted(this.len - offset)
this.len = offset
return right
}
/**
* @param {ContentDeleted} right
* @return {boolean}
*/
mergeWith (right) {
this.len += right.len
return true
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {
addToDeleteSet(transaction.deleteSet, item.id, this.len)
item.deleted = true
}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
encoding.writeVarUint(encoder, this.len - offset)
}
/**
* @return {number}
*/
getRef () {
return 1
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentDeleted}
*/
export const readContentDeleted = decoder => new ContentDeleted(decoding.readVarUint(decoder))

View File

@ -0,0 +1,92 @@
import {
StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js'
/**
* @private
*/
export class ContentEmbed {
/**
* @param {Object} embed
*/
constructor (embed) {
this.embed = embed
}
/**
* @return {number}
*/
getLength () {
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
return [this.embed]
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentEmbed}
*/
copy () {
return new ContentEmbed(this.embed)
}
/**
* @param {number} offset
* @return {ContentEmbed}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {ContentEmbed} right
* @return {boolean}
*/
mergeWith (right) {
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
encoding.writeVarString(encoder, JSON.stringify(this.embed))
}
/**
* @return {number}
*/
getRef () {
return 5
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentEmbed}
*/
export const readContentEmbed = decoder => new ContentEmbed(JSON.parse(decoding.readVarString(decoder)))

View File

@ -0,0 +1,95 @@
import {
Item, StructStore, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js'
/**
* @private
*/
export class ContentFormat {
/**
* @param {string} key
* @param {Object} value
*/
constructor (key, value) {
this.key = key
this.value = value
}
/**
* @return {number}
*/
getLength () {
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
return []
}
/**
* @return {boolean}
*/
isCountable () {
return false
}
/**
* @return {ContentFormat}
*/
copy () {
return new ContentFormat(this.key, this.value)
}
/**
* @param {number} offset
* @return {ContentFormat}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {ContentFormat} right
* @return {boolean}
*/
mergeWith (right) {
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
encoding.writeVarString(encoder, this.key)
encoding.writeVarString(encoder, JSON.stringify(this.value))
}
/**
* @return {number}
*/
getRef () {
return 6
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentFormat}
*/
export const readContentFormat = decoder => new ContentFormat(decoding.readVarString(decoder), JSON.parse(decoding.readVarString(decoder)))

113
src/structs/ContentJSON.js Normal file
View File

@ -0,0 +1,113 @@
import {
Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export class ContentJSON {
/**
* @param {Array<any>} arr
*/
constructor (arr) {
/**
* @type {Array<any>}
*/
this.arr = arr
}
/**
* @return {number}
*/
getLength () {
return this.arr.length
}
/**
* @return {Array<any>}
*/
getContent () {
return this.arr
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentJSON}
*/
copy () {
return new ContentJSON(this.arr)
}
/**
* @param {number} offset
* @return {ContentJSON}
*/
splice (offset) {
const right = new ContentJSON(this.arr.slice(offset))
this.arr = this.arr.slice(0, offset)
return right
}
/**
* @param {ContentJSON} right
* @return {boolean}
*/
mergeWith (right) {
this.arr = this.arr.concat(right.arr)
return true
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
const len = this.arr.length
encoding.writeVarUint(encoder, len - offset)
for (let i = offset; i < len; i++) {
const c = this.arr[i]
encoding.writeVarString(encoder, c === undefined ? 'undefined' : JSON.stringify(c))
}
}
/**
* @return {number}
*/
getRef () {
return 2
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentJSON}
*/
export const readContentJSON = decoder => {
const len = decoding.readVarUint(decoder)
const cs = []
for (let i = 0; i < len; i++) {
const c = decoding.readVarString(decoder)
if (c === 'undefined') {
cs.push(undefined)
} else {
cs.push(JSON.parse(c))
}
}
return new ContentJSON(cs)
}

View File

@ -0,0 +1,96 @@
import {
Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export class ContentString {
/**
* @param {string} str
*/
constructor (str) {
/**
* @type {string}
*/
this.str = str
}
/**
* @return {number}
*/
getLength () {
return this.str.length
}
/**
* @return {Array<any>}
*/
getContent () {
return this.str.split('')
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentString}
*/
copy () {
return new ContentString(this.str)
}
/**
* @param {number} offset
* @return {ContentString}
*/
splice (offset) {
const right = new ContentString(this.str.slice(offset))
this.str = this.str.slice(0, offset)
return right
}
/**
* @param {ContentString} right
* @return {boolean}
*/
mergeWith (right) {
this.str += right.str
return true
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
/**
* @param {StructStore} store
*/
gc (store) {}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
encoding.writeVarString(encoder, offset === 0 ? this.str : this.str.slice(offset))
}
/**
* @return {number}
*/
getRef () {
return 4
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentString}
*/
export const readContentString = decoder => new ContentString(decoding.readVarString(decoder))

161
src/structs/ContentType.js Normal file
View File

@ -0,0 +1,161 @@
import {
readYArray,
readYMap,
readYText,
readYXmlElement,
readYXmlFragment,
readYXmlHook,
readYXmlText,
StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js'
/**
* @type {Array<function(decoding.Decoder):AbstractType<any>>}
* @private
*/
export const typeRefs = [
readYArray,
readYMap,
readYText,
readYXmlElement,
readYXmlFragment,
readYXmlHook,
readYXmlText
]
export const YArrayRefID = 0
export const YMapRefID = 1
export const YTextRefID = 2
export const YXmlElementRefID = 3
export const YXmlFragmentRefID = 4
export const YXmlHookRefID = 5
export const YXmlTextRefID = 6
/**
* @private
*/
export class ContentType {
/**
* @param {AbstractType<YEvent>} type
*/
constructor (type) {
this.type = type
}
/**
* @return {number}
*/
getLength () {
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
return [this.type]
}
/**
* @return {boolean}
*/
isCountable () {
return true
}
/**
* @return {ContentType}
*/
copy () {
return new ContentType(this.type._copy())
}
/**
* @param {number} offset
* @return {ContentType}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {ContentType} right
* @return {boolean}
*/
mergeWith (right) {
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {
this.type._integrate(transaction.doc, item)
}
/**
* @param {Transaction} transaction
*/
delete (transaction) {
let item = this.type._start
while (item !== null) {
if (!item.deleted) {
item.delete(transaction)
} else {
// Whis will be gc'd later and we want to merge it if possible
// We try to merge all deleted items after each transaction,
// but we have no knowledge about that this needs to be merged
// since it is not in transaction.ds. Hence we add it to transaction._mergeStructs
transaction._mergeStructs.add(item.id)
}
item = item.right
}
this.type._map.forEach(item => {
if (!item.deleted) {
item.delete(transaction)
} else {
// same as above
transaction._mergeStructs.add(item.id)
}
})
transaction.changed.delete(this.type)
transaction.changedParentTypes.delete(this.type)
}
/**
* @param {StructStore} store
*/
gc (store) {
let item = this.type._start
while (item !== null) {
item.gc(store, true)
item = item.right
}
this.type._start = null
this.type._map.forEach(/** @param {Item | null} item */ (item) => {
while (item !== null) {
item.gc(store, true)
item = item.left
}
})
this.type._map = new Map()
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
this.type._write(encoder)
}
/**
* @return {number}
*/
getRef () {
return 7
}
}
/**
* @private
*
* @param {decoding.Decoder} decoder
* @return {ContentType}
*/
export const readContentType = decoder => new ContentType(typeRefs[decoding.readVarUint(decoder)](decoder))

View File

@ -21,26 +21,18 @@ export class GC extends AbstractStruct {
* @param {number} length * @param {number} length
*/ */
constructor (id, length) { constructor (id, length) {
super(id) super(id, length)
/**
* @type {number}
*/
this._len = length
this.deleted = true this.deleted = true
} }
get length () {
return this._len
}
delete () {} delete () {}
/** /**
* @param {AbstractStruct} right * @param {GC} right
* @return {boolean} * @return {boolean}
*/ */
mergeWith (right) { mergeWith (right) {
this._len += right.length this.length += right.length
return true return true
} }
@ -57,7 +49,7 @@ export class GC extends AbstractStruct {
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeUint8(encoder, structGCRefNumber) encoding.writeUint8(encoder, structGCRefNumber)
encoding.writeVarUint(encoder, this._len - offset) encoding.writeVarUint(encoder, this.length - offset)
} }
} }
@ -75,15 +67,7 @@ export class GCRef extends AbstractStructRef {
/** /**
* @type {number} * @type {number}
*/ */
this._len = decoding.readVarUint(decoder) this.length = decoding.readVarUint(decoder)
}
get length () {
return this._len
}
missing () {
return [
createID(this.id.client, this.id.clock - 1)
]
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
@ -95,11 +79,11 @@ export class GCRef extends AbstractStructRef {
if (offset > 0) { if (offset > 0) {
// @ts-ignore // @ts-ignore
this.id = createID(this.id.client, this.id.clock + offset) this.id = createID(this.id.client, this.id.clock + offset)
this._len = this._len - offset this.length -= offset
} }
return new GC( return new GC(
this.id, this.id,
this._len this.length
) )
} }
} }

View File

@ -10,14 +10,19 @@ import {
replaceStruct, replaceStruct,
addStruct, addStruct,
addToDeleteSet, addToDeleteSet,
ItemDeleted,
findRootTypeKey, findRootTypeKey,
compareIDs, compareIDs,
getItem, getItem,
getItemType,
getItemCleanEnd, getItemCleanEnd,
getItemCleanStart, getItemCleanStart,
YEvent, StructStore, ID, AbstractType, Transaction // eslint-disable-line readContentDeleted,
readContentBinary,
readContentJSON,
readContentString,
readContentEmbed,
readContentFormat,
readContentType,
ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
@ -27,28 +32,12 @@ import * as maplib from 'lib0/map.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as binary from 'lib0/binary.js' import * as binary from 'lib0/binary.js'
/**
* @param {AbstractItem} left
* @param {AbstractItem} right
* @return {boolean} If true, right is removed from the linked list and should be discarded
*/
export const mergeItemWith = (left, right) => {
if (compareIDs(right.origin, left.lastId) && left.right === right && compareIDs(left.rightOrigin, right.rightOrigin)) {
left.right = right.right
if (left.right !== null) {
left.right.left = left
}
return true
}
return false
}
/** /**
* Split leftItem into two items * Split leftItem into two items
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractItem} leftItem * @param {Item} leftItem
* @param {number} diff * @param {number} diff
* @return {AbstractItem} * @return {Item}
* *
* @function * @function
* @private * @private
@ -56,14 +45,15 @@ export const mergeItemWith = (left, right) => {
export const splitItem = (transaction, leftItem, diff) => { export const splitItem = (transaction, leftItem, diff) => {
const id = leftItem.id const id = leftItem.id
// create rightItem // create rightItem
const rightItem = leftItem.copy( const rightItem = new Item(
createID(id.client, id.clock + diff), createID(id.client, id.clock + diff),
leftItem, leftItem,
createID(id.client, id.clock + diff - 1), createID(id.client, id.clock + diff - 1),
leftItem.right, leftItem.right,
leftItem.rightOrigin, leftItem.rightOrigin,
leftItem.parent, leftItem.parent,
leftItem.parentSub leftItem.parentSub,
leftItem.content.splice(diff)
) )
if (leftItem.deleted) { if (leftItem.deleted) {
rightItem.deleted = true rightItem.deleted = true
@ -80,24 +70,26 @@ export const splitItem = (transaction, leftItem, diff) => {
if (rightItem.parentSub !== null && rightItem.right === null) { if (rightItem.parentSub !== null && rightItem.right === null) {
rightItem.parent._map.set(rightItem.parentSub, rightItem) rightItem.parent._map.set(rightItem.parentSub, rightItem)
} }
leftItem.length = diff
return rightItem return rightItem
} }
/** /**
* Abstract class that represents any content. * Abstract class that represents any content.
*/ */
export class AbstractItem extends AbstractStruct { export class Item extends AbstractStruct {
/** /**
* @param {ID} id * @param {ID} id
* @param {AbstractItem | null} left * @param {Item | null} left
* @param {ID | null} origin * @param {ID | null} origin
* @param {AbstractItem | null} right * @param {Item | null} right
* @param {ID | null} rightOrigin * @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {string | null} parentSub * @param {string | null} parentSub
* @param {AbstractContent} content
*/ */
constructor (id, left, origin, right, rightOrigin, parent, parentSub) { constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
super(id) super(id, content.getLength())
/** /**
* The item that was originally to the left of this item. * The item that was originally to the left of this item.
* @type {ID | null} * @type {ID | null}
@ -106,12 +98,12 @@ export class AbstractItem extends AbstractStruct {
this.origin = origin this.origin = origin
/** /**
* The item that is currently to the left of this item. * The item that is currently to the left of this item.
* @type {AbstractItem | null} * @type {Item | null}
*/ */
this.left = left this.left = left
/** /**
* The item that is currently to the right of this item. * The item that is currently to the right of this item.
* @type {AbstractItem | null} * @type {Item | null}
*/ */
this.right = right this.right = right
/** /**
@ -143,9 +135,12 @@ export class AbstractItem extends AbstractStruct {
/** /**
* If this type's effect is reundone this type refers to the type that undid * If this type's effect is reundone this type refers to the type that undid
* this operation. * this operation.
* @type {AbstractItem | null} * @type {Item | null}
*/ */
this.redone = null this.redone = null
this.content = content
this.length = content.getLength()
this.countable = content.isCountable()
} }
/** /**
@ -159,7 +154,7 @@ export class AbstractItem extends AbstractStruct {
const parentSub = this.parentSub const parentSub = this.parentSub
const length = this.length const length = this.length
/** /**
* @type {AbstractItem|null} * @type {Item|null}
*/ */
let o let o
// set o to the first conflicting item // set o to the first conflicting item
@ -175,11 +170,11 @@ export class AbstractItem extends AbstractStruct {
} }
// TODO: use something like DeleteSet here (a tree implementation would be best) // TODO: use something like DeleteSet here (a tree implementation would be best)
/** /**
* @type {Set<AbstractItem>} * @type {Set<Item>}
*/ */
const conflictingItems = new Set() const conflictingItems = new Set()
/** /**
* @type {Set<AbstractItem>} * @type {Set<Item>}
*/ */
const itemsBeforeOrigin = new Set() const itemsBeforeOrigin = new Set()
// Let c in conflictingItems, b in itemsBeforeOrigin // Let c in conflictingItems, b in itemsBeforeOrigin
@ -238,8 +233,8 @@ export class AbstractItem extends AbstractStruct {
parent._length += length parent._length += length
} }
addStruct(store, this) addStruct(store, this)
this.content.integrate(transaction, this)
maplib.setIfUndefined(transaction.changed, parent, set.create).add(parentSub) maplib.setIfUndefined(transaction.changed, parent, set.create).add(parentSub)
// @ts-ignore
if ((parent._item !== null && parent._item.deleted) || (this.right !== null && parentSub !== null)) { if ((parent._item !== null && parent._item.deleted) || (this.right !== null && parentSub !== null)) {
// delete if parent is deleted or if this is not the current attribute value of parent // delete if parent is deleted or if this is not the current attribute value of parent
this.delete(transaction) this.delete(transaction)
@ -270,29 +265,11 @@ export class AbstractItem extends AbstractStruct {
return n return n
} }
/**
* Creates an Item with the same effect as this Item (without position effect)
*
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @return {AbstractItem}
*
* @private
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
throw error.methodUnimplemented()
}
/** /**
* Redoes the effect of this operation. * Redoes the effect of this operation.
* *
* @param {Transaction} transaction The Yjs instance. * @param {Transaction} transaction The Yjs instance.
* @param {Set<AbstractItem>} redoitems * @param {Set<Item>} redoitems
* *
* @private * @private
*/ */
@ -343,7 +320,7 @@ export class AbstractItem extends AbstractStruct {
right = right.right right = right.right
} }
} }
this.redone = this.copy(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, this.parentSub) this.redone = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, this.parentSub, this.content.copy())
this.redone.integrate(transaction) this.redone.integrate(transaction)
return true return true
} }
@ -354,46 +331,31 @@ export class AbstractItem extends AbstractStruct {
get lastId () { get lastId () {
return createID(this.id.client, this.id.clock + this.length - 1) return createID(this.id.client, this.id.clock + this.length - 1)
} }
/** /**
* Computes the length of this Item. * Try to merge two items
*/
get length () {
return 1
}
/**
* Should return false if this Item is some kind of meta information
* (e.g. format information).
* *
* * Whether this Item should be addressable via `yarray.get(i)` * @param {Item} right
* * Whether this Item should be counted when computing yarray.length * @return {boolean}
*/ */
get countable () { mergeWith (right) {
if (
compareIDs(right.origin, this.lastId) &&
this.right === right &&
compareIDs(this.rightOrigin, right.rightOrigin) &&
this.id.client === right.id.client &&
this.id.clock + this.length === right.id.clock &&
this.deleted === right.deleted &&
this.content.constructor === right.content.constructor &&
this.content.mergeWith(right.content)
) {
this.right = right.right
if (this.right !== null) {
this.right.left = this
}
this.length += right.length
return true return true
} }
return false
/**
* Do not call directly. Always split via StructStore!
*
* Splits this Item so that another Item can be inserted in-between.
* This must be overwritten if _length > 1
* Returns right part after split
*
* (see {@link ItemJSON}/{@link ItemString} for implementation)
*
* Does not integrate the struct, nor store it in struct store.
*
* This method should only be cally by StructStore.
*
* @param {Transaction} transaction
* @param {number} diff
* @return {AbstractItem}
*
* @private
*/
splitAt (transaction, diff) {
throw error.methodUnimplemented()
} }
/** /**
@ -411,16 +373,10 @@ export class AbstractItem extends AbstractStruct {
this.deleted = true this.deleted = true
addToDeleteSet(transaction.deleteSet, this.id, this.length) addToDeleteSet(transaction.deleteSet, this.id, this.length)
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub) maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
this.content.delete(transaction)
} }
} }
/**
* @param {StructStore} store
*
* @private
*/
gcChildren (store) { }
/** /**
* @param {StructStore} store * @param {StructStore} store
* @param {boolean} parentGCd * @param {boolean} parentGCd
@ -431,30 +387,12 @@ export class AbstractItem extends AbstractStruct {
if (!this.deleted) { if (!this.deleted) {
throw error.unexpectedCase() throw error.unexpectedCase()
} }
let r this.content.gc(store)
if (parentGCd) { if (parentGCd) {
r = new GC(this.id, this.length) replaceStruct(store, this, new GC(this.id, this.length))
} else { } else {
r = new ItemDeleted(this.id, this.left, this.origin, this.right, this.rightOrigin, this.parent, this.parentSub, this.length) this.content = new ContentDeleted(this.length)
if (r.right !== null) {
r.right.left = r
} else if (r.parentSub !== null) {
r.parent._map.set(r.parentSub, r)
} }
if (r.left !== null) {
r.left.right = r
} else if (r.parentSub === null) {
r.parent._start = r
}
}
replaceStruct(store, this, r)
}
/**
* @return {Array<any>}
*/
getContent () {
throw error.methodUnimplemented()
} }
/** /**
@ -465,15 +403,14 @@ export class AbstractItem extends AbstractStruct {
* *
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {encoding.Encoder} encoder The encoder to write data to.
* @param {number} offset * @param {number} offset
* @param {number} encodingRef
* *
* @private * @private
*/ */
write (encoder, offset, encodingRef) { write (encoder, offset) {
const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
const rightOrigin = this.rightOrigin const rightOrigin = this.rightOrigin
const parentSub = this.parentSub const parentSub = this.parentSub
const info = (encodingRef & binary.BITS5) | const info = (this.content.getRef() & binary.BITS5) |
(origin === null ? 0 : binary.BIT8) | // origin is defined (origin === null ? 0 : binary.BIT8) | // origin is defined
(rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined (rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
(parentSub === null ? 0 : binary.BIT6) // parentSub is non-null (parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
@ -489,26 +426,129 @@ export class AbstractItem extends AbstractStruct {
if (parent._item === null) { if (parent._item === null) {
// parent type on y._map // parent type on y._map
// find the correct key // find the correct key
// @ts-ignore we know that y exists
const ykey = findRootTypeKey(parent) const ykey = findRootTypeKey(parent)
encoding.writeVarUint(encoder, 1) // write parentYKey encoding.writeVarUint(encoder, 1) // write parentYKey
encoding.writeVarString(encoder, ykey) encoding.writeVarString(encoder, ykey)
} else { } else {
encoding.writeVarUint(encoder, 0) // write parent id encoding.writeVarUint(encoder, 0) // write parent id
// @ts-ignore _item is defined because parent is integrated
writeID(encoder, parent._item.id) writeID(encoder, parent._item.id)
} }
if (parentSub !== null) { if (parentSub !== null) {
encoding.writeVarString(encoder, parentSub) encoding.writeVarString(encoder, parentSub)
} }
} }
this.content.write(encoder, offset)
}
}
/**
* @param {decoding.Decoder} decoder
* @param {number} info
*/
const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
/**
* A lookup map for reading Item content.
*
* @type {Array<function(decoding.Decoder):AbstractContent>}
*/
export const contentRefs = [
() => { throw error.unexpectedCase() }, // GC is not ItemContent
readContentDeleted,
readContentJSON,
readContentBinary,
readContentString,
readContentEmbed,
readContentFormat,
readContentType
]
/**
* Do not implement this class!
*/
export class AbstractContent {
/**
* @return {number}
*/
getLength () {
throw error.methodUnimplemented()
}
/**
* @return {Array<any>}
*/
getContent () {
throw error.methodUnimplemented()
}
/**
* Should return false if this Item is some kind of meta information
* (e.g. format information).
*
* * Whether this Item should be addressable via `yarray.get(i)`
* * Whether this Item should be counted when computing yarray.length
*
* @return {boolean}
*/
isCountable () {
throw error.methodUnimplemented()
}
/**
* @return {AbstractContent}
*/
copy () {
throw error.methodUnimplemented()
}
/**
* @param {number} offset
* @return {AbstractContent}
*/
splice (offset) {
throw error.methodUnimplemented()
}
/**
* @param {AbstractContent} right
* @return {boolean}
*/
mergeWith (right) {
throw error.methodUnimplemented()
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {
throw error.methodUnimplemented()
}
/**
* @param {Transaction} transaction
*/
delete (transaction) {
throw error.methodUnimplemented()
}
/**
* @param {StructStore} store
*/
gc (store) {
throw error.methodUnimplemented()
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
throw error.methodUnimplemented()
}
/**
* @return {number}
*/
getRef () {
throw error.methodUnimplemented()
} }
} }
/** /**
* @private * @private
*/ */
export class AbstractItemRef extends AbstractStructRef { export class ItemRef extends AbstractStructRef {
/** /**
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {ID} id * @param {ID} id
@ -558,73 +598,44 @@ export class AbstractItemRef extends AbstractStructRef {
if (this.parent !== null) { if (this.parent !== null) {
missing.push(this.parent) missing.push(this.parent)
} }
}
}
/**
* @param {AbstractItemRef} item
* @param {number} offset
*
* @function
* @private
*/
export const changeItemRefOffset = (item, offset) => {
item.id = createID(item.id.client, item.id.clock + offset)
item.left = createID(item.id.client, item.id.clock - 1)
}
export class ItemParams {
/** /**
* @param {AbstractItem?} left * @type {AbstractContent}
* @param {AbstractItem?} right
* @param {AbstractType<YEvent>?} parent
* @param {string|null} parentSub
*/ */
constructor (left, right, parent, parentSub) { this.content = readItemContent(decoder, info)
this.left = left this.length = this.content.getLength()
this.right = right
this.parent = parent
this.parentSub = parentSub
} }
} /**
/**
* Outsourcing some of the logic of computing the item params from a received struct.
* If parent === null, it is expected to gc the read struct. Otherwise apply it.
*
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {ID|null} leftid * @param {number} offset
* @param {ID|null} rightid * @return {Item|GC}
* @param {ID|null} parentid
* @param {string|null} parentSub
* @param {string|null} parentYKey
* @return {ItemParams}
*
* @private
* @function
*/ */
export const computeItemParams = (transaction, store, leftid, rightid, parentid, parentSub, parentYKey) => { toStruct (transaction, store, offset) {
const left = leftid === null ? null : getItemCleanEnd(transaction, store, leftid) if (offset > 0) {
const right = rightid === null ? null : getItemCleanStart(transaction, store, rightid) /**
* @type {ID}
*/
const id = this.id
this.id = createID(id.client, id.clock + offset)
this.left = createID(this.id.client, this.id.clock - 1)
this.content = this.content.splice(offset)
}
const left = this.left === null ? null : getItemCleanEnd(transaction, store, this.left)
const right = this.right === null ? null : getItemCleanStart(transaction, store, this.right)
let parent = null let parent = null
if (parentid !== null) { let parentSub = this.parentSub
const parentItem = getItemType(store, parentid) if (this.parent !== null) {
switch (parentItem.constructor) { const parentItem = getItem(store, this.parent)
case ItemDeleted:
case GC:
break
default:
// Edge case: toStruct is called with an offset > 0. In this case left is defined. // 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 // 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 // deleted. This is why we check if left is GC'd. Strictly we don't have
// to check if right is GC'd, but we will in case we run into future issues // 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)) { if (!parentItem.deleted && (left === null || left.constructor !== GC) && (right === null || right.constructor !== GC)) {
parent = parentItem.type parent = /** @type {ContentType} */ (parentItem.content).type
} }
} } else if (this.parentYKey !== null) {
} else if (parentYKey !== null) { parent = transaction.doc.get(this.parentYKey)
parent = transaction.doc.get(parentYKey)
} else if (left !== null) { } else if (left !== null) {
if (left.constructor !== GC) { if (left.constructor !== GC) {
parent = left.parent parent = left.parent
@ -638,5 +649,18 @@ export const computeItemParams = (transaction, store, leftid, rightid, parentid,
} else { } else {
throw error.unexpectedCase() throw error.unexpectedCase()
} }
return new ItemParams(left, right, parent, parentSub)
return parent === null
? new GC(this.id, this.length)
: new Item(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.content
)
}
} }

View File

@ -1,99 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
GC,
StructStore, Transaction, AbstractType, ID // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as buffer from 'lib0/buffer.js'
/**
* @private
*/
export const structBinaryRefNumber = 1
/**
* @private
*/
export class ItemBinary extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {Uint8Array} content
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
this.content = content
}
getContent () {
return [this.content]
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemBinary(id, left, origin, right, rightOrigin, parent, parentSub, this.content)
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structBinaryRefNumber)
encoding.writeVarUint8Array(encoder, this.content)
}
}
/**
* @private
*/
export class ItemBinaryRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {Uint8Array}
*/
this.content = buffer.copyUint8Array(decoding.readVarUint8Array(decoder))
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemBinary|GC}
*/
toStruct (transaction, store, offset) {
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemBinary(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.content
)
}
}

View File

@ -1,155 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
changeItemRefOffset,
GC,
splitItem,
addToDeleteSet,
mergeItemWith,
StructStore, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export const structDeletedRefNumber = 2
/**
* @private
*/
export class ItemDeleted extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {number} length
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, length) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
this._len = length
this.deleted = true
}
get length () {
return this._len
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemDeleted(id, left, origin, right, rightOrigin, parent, parentSub, this.length)
}
/**
* @param {Transaction} transaction
*/
integrate (transaction) {
super.integrate(transaction)
addToDeleteSet(transaction.deleteSet, this.id, this.length)
}
/**
* @param {Transaction} transaction
* @param {number} diff
*/
splitAt (transaction, diff) {
/**
* @type {ItemDeleted}
*/
// @ts-ignore
const right = splitItem(transaction, this, diff)
right._len -= diff
this._len = diff
return right
}
/**
* @param {ItemDeleted} right
* @return {boolean}
*/
mergeWith (right) {
if (mergeItemWith(this, right)) {
this._len += right._len
return true
}
return false
}
/**
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
gc (store, parentGCd) {
if (parentGCd) {
super.gc(store, parentGCd)
}
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structDeletedRefNumber)
encoding.writeVarUint(encoder, this.length - offset)
}
}
/**
* @private
*/
export class ItemDeletedRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {number}
*/
this.len = decoding.readVarUint(decoder)
}
get length () {
return this.len
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemDeleted|GC}
*/
toStruct (transaction, store, offset) {
if (offset > 0) {
changeItemRefOffset(this, offset)
this.len = this.len - offset
}
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemDeleted(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.len
)
}
}

View File

@ -1,95 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
GC,
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export const structEmbedRefNumber = 3
/**
* @private
*/
export class ItemEmbed extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {Object} embed
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, embed) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
this.embed = embed
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemEmbed(id, left, origin, right, rightOrigin, parent, parentSub, this.embed)
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structEmbedRefNumber)
encoding.writeVarString(encoder, JSON.stringify(this.embed))
}
}
/**
* @private
*/
export class ItemEmbedRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {Object}
*/
this.embed = JSON.parse(decoding.readVarString(decoder))
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemEmbed|GC}
*/
toStruct (transaction, store, offset) {
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemEmbed(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.embed
)
}
}

View File

@ -1,103 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
GC,
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export const structFormatRefNumber = 4
/**
* @private
*/
export class ItemFormat extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {string} key
* @param {any} value
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, key, value) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
this.key = key
this.value = value
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemFormat(id, left, origin, right, rightOrigin, parent, parentSub, this.key, this.value)
}
get countable () {
return false
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structFormatRefNumber)
encoding.writeVarString(encoder, this.key)
encoding.writeVarString(encoder, JSON.stringify(this.value))
}
}
/**
* @private
*/
export class ItemFormatRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {string}
*/
this.key = decoding.readVarString(decoder)
this.value = JSON.parse(decoding.readVarString(decoder))
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemFormat|GC}
*/
toStruct (transaction, store, offset) {
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemFormat(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.key,
this.value
)
}
}

View File

@ -1,153 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
splitItem,
changeItemRefOffset,
GC,
mergeItemWith,
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export const structJSONRefNumber = 5
/**
* @private
*/
export class ItemJSON extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {Array<any>} content
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
/**
* @type {Array<any>}
*/
this.content = content
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemJSON(id, left, origin, right, rightOrigin, parent, parentSub, this.content)
}
get length () {
return this.content.length
}
getContent () {
return this.content
}
/**
* @param {Transaction} transaction
* @param {number} diff
*/
splitAt (transaction, diff) {
/**
* @type {ItemJSON}
*/
// @ts-ignore
const right = splitItem(transaction, this, diff)
right.content = this.content.splice(diff)
return right
}
/**
* @param {ItemJSON} right
* @return {boolean}
*/
mergeWith (right) {
if (mergeItemWith(this, right)) {
this.content = this.content.concat(right.content)
return true
}
return false
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structJSONRefNumber)
const len = this.content.length
encoding.writeVarUint(encoder, len - offset)
for (let i = offset; i < len; i++) {
const c = this.content[i]
encoding.writeVarString(encoder, c === undefined ? 'undefined' : JSON.stringify(c))
}
}
}
/**
* @private
*/
export class ItemJSONRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
const len = decoding.readVarUint(decoder)
const cs = []
for (let i = 0; i < len; i++) {
const c = decoding.readVarString(decoder)
if (c === 'undefined') {
cs.push(undefined)
} else {
cs.push(JSON.parse(c))
}
}
/**
* @type {Array<any>}
*/
this.content = cs
}
get length () {
return this.content.length
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemJSON|GC}
*/
toStruct (transaction, store, offset) {
if (offset > 0) {
changeItemRefOffset(this, offset)
this.content = this.content.slice(offset)
}
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemJSON(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.content
)
}
}

View File

@ -1,138 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
splitItem,
changeItemRefOffset,
GC,
mergeItemWith,
Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
export const structStringRefNumber = 6
/**
* @private
*/
export class ItemString extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {string} string
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, string) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
/**
* @type {string}
*/
this.string = string
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemString(id, left, origin, right, rightOrigin, parent, parentSub, this.string)
}
getContent () {
return this.string.split('')
}
get length () {
return this.string.length
}
/**
* @param {Transaction} transaction
* @param {number} diff
* @return {ItemString}
*/
splitAt (transaction, diff) {
/**
* @type {ItemString}
*/
// @ts-ignore
const right = splitItem(transaction, this, diff)
right.string = this.string.slice(diff)
this.string = this.string.slice(0, diff)
return right
}
/**
* @param {ItemString} right
* @return {boolean}
*/
mergeWith (right) {
if (mergeItemWith(this, right)) {
this.string += right.string
return true
}
return false
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structStringRefNumber)
encoding.writeVarString(encoder, offset === 0 ? this.string : this.string.slice(offset))
}
}
/**
* @private
*/
export class ItemStringRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
/**
* @type {string}
*/
this.string = decoding.readVarString(decoder)
}
get length () {
return this.string.length
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemString|GC}
*/
toStruct (transaction, store, offset) {
if (offset > 0) {
changeItemRefOffset(this, offset)
this.string = this.string.slice(offset)
}
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemString(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.string
)
}
}

View File

@ -1,199 +0,0 @@
import {
AbstractItem,
AbstractItemRef,
computeItemParams,
readYArray,
readYMap,
readYText,
readYXmlElement,
readYXmlFragment,
readYXmlHook,
readYXmlText,
StructStore, GC, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
import * as decoding from 'lib0/decoding.js'
/**
* @private
*/
export const structTypeRefNumber = 7
/**
* @type {Array<function(decoding.Decoder):AbstractType<any>>}
* @private
*/
export const typeRefs = [
readYArray,
readYMap,
readYText,
readYXmlElement,
readYXmlFragment,
readYXmlHook,
readYXmlText
]
export const YArrayRefID = 0
export const YMapRefID = 1
export const YTextRefID = 2
export const YXmlElementRefID = 3
export const YXmlFragmentRefID = 4
export const YXmlHookRefID = 5
export const YXmlTextRefID = 6
/**
* @private
*/
export class ItemType extends AbstractItem {
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @param {AbstractType<any>} type
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, type) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
this.type = type
}
getContent () {
return [this.type]
}
/**
* @param {ID} id
* @param {AbstractItem | null} left
* @param {ID | null} origin
* @param {AbstractItem | null} right
* @param {ID | null} rightOrigin
* @param {AbstractType<any>} parent
* @param {string | null} parentSub
* @return {ItemType}
*/
copy (id, left, origin, right, rightOrigin, parent, parentSub) {
return new ItemType(id, left, origin, right, rightOrigin, parent, parentSub, this.type._copy())
}
/**
* @param {Transaction} transaction
*/
integrate (transaction) {
super.integrate(transaction)
this.type._integrate(transaction.doc, this)
}
/**
* @param {encoding.Encoder} encoder
* @param {number} offset
*/
write (encoder, offset) {
super.write(encoder, offset, structTypeRefNumber)
this.type._write(encoder)
}
/**
* Mark this Item as deleted.
*
* @param {Transaction} transaction The Yjs instance
* @private
*/
delete (transaction) {
if (!this.deleted) {
super.delete(transaction)
let item = this.type._start
while (item !== null) {
if (!item.deleted) {
item.delete(transaction)
} else {
// Whis will be gc'd later and we want to merge it if possible
// We try to merge all deleted items after each transaction,
// but we have no knowledge about that this needs to be merged
// since it is not in transaction.ds. Hence we add it to transaction._mergeStructs
transaction._mergeStructs.add(item.id)
}
item = item.right
}
this.type._map.forEach(item => {
if (!item.deleted) {
item.delete(transaction)
} else {
// same as above
transaction._mergeStructs.add(item.id)
}
})
transaction.changed.delete(this.type)
transaction.changedParentTypes.delete(this.type)
}
}
/**
* @param {StructStore} store
*/
gcChildren (store) {
let item = this.type._start
while (item !== null) {
item.gc(store, true)
item = item.right
}
this.type._start = null
this.type._map.forEach(item => {
while (item !== null) {
item.gc(store, true)
// @ts-ignore
item = item.left
}
})
this._map = new Map()
}
/**
* @param {StructStore} store
* @param {boolean} parentGCd
*/
gc (store, parentGCd) {
this.gcChildren(store)
super.gc(store, parentGCd)
}
}
/**
* @private
*/
export class ItemTypeRef extends AbstractItemRef {
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
*/
constructor (decoder, id, info) {
super(decoder, id, info)
const typeRef = decoding.readVarUint(decoder)
/**
* @type {AbstractType<any>}
*/
this.type = typeRefs[typeRef](decoder)
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {number} offset
* @return {ItemType|GC}
*/
toStruct (transaction, store, offset) {
const { left, right, parent, parentSub } = computeItemParams(transaction, store, this.left, this.right, this.parent, this.parentSub, this.parentYKey)
return parent === null
? new GC(this.id, this.length)
: new ItemType(
this.id,
left,
this.left,
right,
this.right,
parent,
parentSub,
this.type
)
}
}

View File

@ -4,14 +4,14 @@ import {
callEventHandlerListeners, callEventHandlerListeners,
addEventHandlerListener, addEventHandlerListener,
createEventHandler, createEventHandler,
ItemType,
nextID, nextID,
isVisible, isVisible,
ItemJSON, ContentType,
ItemBinary, ContentJSON,
ContentBinary,
createID, createID,
getItemCleanStart, getItemCleanStart,
Doc, Snapshot, Transaction, EventHandler, YEvent, AbstractItem, // eslint-disable-line Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
@ -49,17 +49,17 @@ export const callTypeObservers = (type, transaction, event) => {
export class AbstractType { export class AbstractType {
constructor () { constructor () {
/** /**
* @type {ItemType|null} * @type {Item|null}
*/ */
this._item = null this._item = null
/** /**
* @private * @private
* @type {Map<string,AbstractItem>} * @type {Map<string,Item>}
*/ */
this._map = new Map() this._map = new Map()
/** /**
* @private * @private
* @type {AbstractItem|null} * @type {Item|null}
*/ */
this._start = null this._start = null
/** /**
@ -88,7 +88,7 @@ export class AbstractType {
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Doc} y The Yjs instance * @param {Doc} y The Yjs instance
* @param {ItemType|null} item * @param {Item|null} item
* @private * @private
*/ */
_integrate (y, item) { _integrate (y, item) {
@ -187,7 +187,7 @@ export const typeListToArray = type => {
let n = type._start let n = type._start
while (n !== null) { while (n !== null) {
if (n.countable && !n.deleted) { if (n.countable && !n.deleted) {
const c = n.getContent() const c = n.content.getContent()
for (let i = 0; i < c.length; i++) { for (let i = 0; i < c.length; i++) {
cs.push(c[i]) cs.push(c[i])
} }
@ -210,7 +210,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
let n = type._start let n = type._start
while (n !== null) { while (n !== null) {
if (n.countable && isVisible(n, snapshot)) { if (n.countable && isVisible(n, snapshot)) {
const c = n.getContent() const c = n.content.getContent()
for (let i = 0; i < c.length; i++) { for (let i = 0; i < c.length; i++) {
cs.push(c[i]) cs.push(c[i])
} }
@ -234,7 +234,7 @@ export const typeListForEach = (type, f) => {
let n = type._start let n = type._start
while (n !== null) { while (n !== null) {
if (n.countable && !n.deleted) { if (n.countable && !n.deleted) {
const c = n.getContent() const c = n.content.getContent()
for (let i = 0; i < c.length; i++) { for (let i = 0; i < c.length; i++) {
f(c[i], index++, type) f(c[i], index++, type)
} }
@ -295,7 +295,7 @@ export const typeListCreateIterator = type => {
} }
} }
// we found n, so we can set currentContent // we found n, so we can set currentContent
currentContent = n.getContent() currentContent = n.content.getContent()
currentContentIndex = 0 currentContentIndex = 0
n = n.right // we used the content of n, now iterate to next n = n.right // we used the content of n, now iterate to next
} }
@ -328,7 +328,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
let n = type._start let n = type._start
while (n !== null) { while (n !== null) {
if (n.countable && isVisible(n, snapshot)) { if (n.countable && isVisible(n, snapshot)) {
const c = n.getContent() const c = n.content.getContent()
for (let i = 0; i < c.length; i++) { for (let i = 0; i < c.length; i++) {
f(c[i], index++, type) f(c[i], index++, type)
} }
@ -349,7 +349,7 @@ export const typeListGet = (type, index) => {
for (let n = type._start; n !== null; n = n.right) { for (let n = type._start; n !== null; n = n.right) {
if (!n.deleted && n.countable) { if (!n.deleted && n.countable) {
if (index < n.length) { if (index < n.length) {
return n.getContent()[index] return n.content.getContent()[index]
} }
index -= n.length index -= n.length
} }
@ -359,7 +359,7 @@ export const typeListGet = (type, index) => {
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {AbstractItem?} referenceItem * @param {Item?} referenceItem
* @param {Array<Object<string,any>|Array<any>|boolean|number|string|Uint8Array>} content * @param {Array<Object<string,any>|Array<any>|boolean|number|string|Uint8Array>} content
* *
* @private * @private
@ -374,7 +374,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
let jsonContent = [] let jsonContent = []
const packJsonContent = () => { const packJsonContent = () => {
if (jsonContent.length > 0) { if (jsonContent.length > 0) {
left = new ItemJSON(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, jsonContent) left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new ContentJSON(jsonContent))
left.integrate(transaction) left.integrate(transaction)
jsonContent = [] jsonContent = []
} }
@ -393,12 +393,12 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
switch (c.constructor) { switch (c.constructor) {
case Uint8Array: case Uint8Array:
case ArrayBuffer: case ArrayBuffer:
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 = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
left.integrate(transaction) left.integrate(transaction)
break break
default: default:
if (c instanceof AbstractType) { if (c instanceof AbstractType) {
left = new ItemType(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, c) left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new ContentType(c))
left.integrate(transaction) left.integrate(transaction)
} else { } else {
throw new Error('Unexpected content type in insert operation') throw new Error('Unexpected content type in insert operation')
@ -501,28 +501,30 @@ export const typeMapDelete = (transaction, parent, key) => {
*/ */
export const typeMapSet = (transaction, parent, key, value) => { export const typeMapSet = (transaction, parent, key, value) => {
const left = parent._map.get(key) || null const left = parent._map.get(key) || null
let content
if (value == null) { if (value == null) {
new ItemJSON(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, [value]).integrate(transaction) content = new ContentJSON([value])
return } else {
}
switch (value.constructor) { switch (value.constructor) {
case Number: case Number:
case Object: case Object:
case Boolean: case Boolean:
case Array: case Array:
case String: case String:
new ItemJSON(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, [value]).integrate(transaction) content = new ContentJSON([value])
break break
case Uint8Array: case Uint8Array:
new ItemBinary(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, value).integrate(transaction) content = new ContentBinary(value)
break break
default: default:
if (value instanceof AbstractType) { if (value instanceof AbstractType) {
new ItemType(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, value).integrate(transaction) content = new ContentType(value)
} else { } else {
throw new Error('Unexpected content type') throw new Error('Unexpected content type')
} }
} }
}
new Item(nextID(transaction), left, left === null ? null : left.lastId, null, null, parent, key, content).integrate(transaction)
} }
/** /**
@ -535,7 +537,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
*/ */
export const typeMapGet = (parent, key) => { export const typeMapGet = (parent, key) => {
const val = parent._map.get(key) const val = parent._map.get(key)
return val !== undefined && !val.deleted ? val.getContent()[0] : undefined return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
} }
/** /**
@ -552,7 +554,7 @@ export const typeMapGetAll = (parent) => {
let res = {} let res = {}
for (const [key, value] of parent._map) { for (const [key, value] of parent._map) {
if (!value.deleted) { if (!value.deleted) {
res[key] = value.getContent()[value.length - 1] res[key] = value.content.getContent()[value.length - 1]
} }
} }
return res return res
@ -585,11 +587,11 @@ export const typeMapGetSnapshot = (parent, key, snapshot) => {
while (v !== null && (!snapshot.sm.has(v.id.client) || v.id.clock >= (snapshot.sm.get(v.id.client) || 0))) { while (v !== null && (!snapshot.sm.has(v.id.client) || v.id.clock >= (snapshot.sm.get(v.id.client) || 0))) {
v = v.left v = v.left
} }
return v !== null && isVisible(v, snapshot) ? v.getContent()[v.length - 1] : undefined return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
} }
/** /**
* @param {Map<string,AbstractItem>} map * @param {Map<string,Item>} map
* @return {IterableIterator<Array<any>>} * @return {IterableIterator<Array<any>>}
* *
* @private * @private

View File

@ -15,7 +15,7 @@ import {
YArrayRefID, YArrayRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, Transaction, ItemType, // eslint-disable-line Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
@ -59,14 +59,13 @@ export class YArray extends AbstractType {
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Doc} y The Yjs instance * @param {Doc} y The Yjs instance
* @param {ItemType} item * @param {Item} item
* *
* @private * @private
*/ */
_integrate (y, item) { _integrate (y, item) {
super._integrate(y, item) super._integrate(y, item)
// @ts-ignore this.insert(0, /** @type {Array} */ (this._prelimContent))
this.insert(0, this._prelimContent)
this._prelimContent = null this._prelimContent = null
} }
get length () { get length () {
@ -106,8 +105,7 @@ export class YArray extends AbstractType {
typeListInsertGenerics(transaction, this, index, content) typeListInsertGenerics(transaction, this, index, content)
}) })
} else { } else {
// @ts-ignore _prelimContent is defined because this is not yet integrated /** @type {Array} */ (this._prelimContent).splice(index, 0, ...content)
this._prelimContent.splice(index, 0, ...content)
} }
} }
@ -132,8 +130,7 @@ export class YArray extends AbstractType {
typeListDelete(transaction, this, index, length) typeListDelete(transaction, this, index, length)
}) })
} else { } else {
// @ts-ignore _prelimContent is defined because this is not yet integrated /** @type {Array} */ (this._prelimContent).splice(index, length)
this._prelimContent.splice(index, length)
} }
} }
@ -175,8 +172,7 @@ export class YArray extends AbstractType {
* callback function * callback function
*/ */
map (f) { map (f) {
// @ts-ignore return typeListMap(this, /** @type {any} */ (f))
return typeListMap(this, f)
} }
/** /**

View File

@ -14,7 +14,7 @@ import {
YMapRefID, YMapRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, Transaction, ItemType, // eslint-disable-line Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
@ -61,14 +61,13 @@ export class YMap extends AbstractType {
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Doc} y The Yjs instance * @param {Doc} y The Yjs instance
* @param {ItemType} item * @param {Item} item
* *
* @private * @private
*/ */
_integrate (y, item) { _integrate (y, item) {
super._integrate(y, item) super._integrate(y, item)
// @ts-ignore for (let [key, value] of /** @type {Map<string, any>} */ (this._prelimContent)) {
for (let [key, value] of this._prelimContent) {
this.set(key, value) this.set(key, value)
} }
this._prelimContent = null this._prelimContent = null
@ -97,7 +96,7 @@ export class YMap extends AbstractType {
const map = {} const map = {}
for (let [key, item] of this._map) { for (let [key, item] of this._map) {
if (!item.deleted) { if (!item.deleted) {
const v = item.getContent()[0] const v = item.content.getContent()[item.length - 1]
map[key] = v instanceof AbstractType ? v.toJSON() : v map[key] = v instanceof AbstractType ? v.toJSON() : v
} }
} }
@ -119,7 +118,7 @@ export class YMap extends AbstractType {
* @return {Iterator<string>} * @return {Iterator<string>}
*/ */
values () { values () {
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].getContent()[v[1].length - 1]) return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
} }
/** /**
@ -128,7 +127,7 @@ export class YMap extends AbstractType {
* @return {IterableIterator<any>} * @return {IterableIterator<any>}
*/ */
entries () { entries () {
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => [v[0], v[1].getContent()[v[1].length - 1]]) return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => [v[0], v[1].content.getContent()[v[1].length - 1]])
} }
/** /**
@ -149,8 +148,7 @@ export class YMap extends AbstractType {
typeMapDelete(transaction, this, key) typeMapDelete(transaction, this, key)
}) })
} else { } else {
// @ts-ignore /** @type {Map<string, any>} */ (this._prelimContent).delete(key)
this._prelimContent.delete(key)
} }
} }
@ -166,8 +164,7 @@ export class YMap extends AbstractType {
typeMapSet(transaction, this, key, value) typeMapSet(transaction, this, key, value)
}) })
} else { } else {
// @ts-ignore /** @type {Map<string, any>} */ (this._prelimContent).set(key, value)
this._prelimContent.set(key, value)
} }
return value return value
} }
@ -179,8 +176,7 @@ export class YMap extends AbstractType {
* @return {T|undefined} * @return {T|undefined}
*/ */
get (key) { get (key) {
// @ts-ignore return /** @type {any} */ (typeMapGet(this, key))
return typeMapGet(this, key)
} }
/** /**

View File

@ -5,9 +5,6 @@
import { import {
YEvent, YEvent,
ItemEmbed,
ItemString,
ItemFormat,
AbstractType, AbstractType,
nextID, nextID,
createID, createID,
@ -16,7 +13,10 @@ import {
YTextRefID, YTextRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line ContentEmbed,
ContentFormat,
ContentString,
Doc, Item, Snapshot, StructStore, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
@ -24,8 +24,8 @@ import * as encoding from 'lib0/encoding.js'
export class ItemListPosition { export class ItemListPosition {
/** /**
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
*/ */
constructor (left, right) { constructor (left, right) {
this.left = left this.left = left
@ -35,8 +35,8 @@ export class ItemListPosition {
export class ItemTextListPosition extends ItemListPosition { export class ItemTextListPosition extends ItemListPosition {
/** /**
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
*/ */
constructor (left, right, currentAttributes) { constructor (left, right, currentAttributes) {
@ -47,8 +47,8 @@ export class ItemTextListPosition extends ItemListPosition {
export class ItemInsertionResult extends ItemListPosition { export class ItemInsertionResult extends ItemListPosition {
/** /**
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} negatedAttributes * @param {Map<string,any>} negatedAttributes
*/ */
constructor (left, right, negatedAttributes) { constructor (left, right, negatedAttributes) {
@ -61,8 +61,8 @@ export class ItemInsertionResult extends ItemListPosition {
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {number} count * @param {number} count
* @return {ItemTextListPosition} * @return {ItemTextListPosition}
* *
@ -71,9 +71,9 @@ export class ItemInsertionResult extends ItemListPosition {
*/ */
const findNextPosition = (transaction, store, currentAttributes, left, right, count) => { const findNextPosition = (transaction, store, currentAttributes, left, right, count) => {
while (right !== null && count > 0) { while (right !== null && count > 0) {
switch (right.constructor) { switch (right.content.constructor) {
case ItemEmbed: case ContentEmbed:
case ItemString: case ContentString:
if (!right.deleted) { if (!right.deleted) {
if (count < right.length) { if (count < right.length) {
// split right // split right
@ -82,10 +82,9 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
count -= right.length count -= right.length
} }
break break
case ItemFormat: case ContentFormat:
if (!right.deleted) { if (!right.deleted) {
// @ts-ignore right is ItemFormat updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (right.content))
updateCurrentAttributes(currentAttributes, right)
} }
break break
} }
@ -117,8 +116,8 @@ const findPosition = (transaction, store, parent, index) => {
* *
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} negatedAttributes * @param {Map<string,any>} negatedAttributes
* @return {ItemListPosition} * @return {ItemListPosition}
* *
@ -130,21 +129,19 @@ const insertNegatedAttributes = (transaction, parent, left, right, negatedAttrib
while ( while (
right !== null && ( right !== null && (
right.deleted === true || ( right.deleted === true || (
right.constructor === ItemFormat && right.content.constructor === ContentFormat &&
// @ts-ignore right is ItemFormat (negatedAttributes.get(/** @type {ContentFormat} */ (right.content).key) === /** @type {ContentFormat} */ (right.content).value)
(negatedAttributes.get(right.key) === right.value)
) )
) )
) { ) {
if (!right.deleted) { if (!right.deleted) {
// @ts-ignore right is ItemFormat negatedAttributes.delete(/** @type {ContentFormat} */ (right.content).key)
negatedAttributes.delete(right.key)
} }
left = right left = right
right = right.right right = right.right
} }
for (let [key, val] of negatedAttributes) { for (let [key, val] of negatedAttributes) {
left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val) left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new ContentFormat(key, val))
left.integrate(transaction) left.integrate(transaction)
} }
return { left, right } return { left, right }
@ -152,14 +149,13 @@ const insertNegatedAttributes = (transaction, parent, left, right, negatedAttrib
/** /**
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {ItemFormat} item * @param {ContentFormat} format
* *
* @private * @private
* @function * @function
*/ */
const updateCurrentAttributes = (currentAttributes, item) => { const updateCurrentAttributes = (currentAttributes, format) => {
const value = item.value const { key, value } = format
const key = item.key
if (value === null) { if (value === null) {
currentAttributes.delete(key) currentAttributes.delete(key)
} else { } else {
@ -168,8 +164,8 @@ const updateCurrentAttributes = (currentAttributes, item) => {
} }
/** /**
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemListPosition} * @return {ItemListPosition}
@ -184,11 +180,9 @@ const minimizeAttributeChanges = (left, right, currentAttributes, attributes) =>
break break
} else if (right.deleted) { } else if (right.deleted) {
// continue // continue
// @ts-ignore right is ItemFormat } else if (right.content.constructor === ContentFormat && (attributes[(/** @type {ContentFormat} */ (right.content)).key] || null) === /** @type {ContentFormat} */ (right.content).value) {
} else if (right.constructor === ItemFormat && (attributes[right.key] || null) === right.value) {
// found a format, update currentAttributes and continue // found a format, update currentAttributes and continue
// @ts-ignore right is ItemFormat updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (right.content))
updateCurrentAttributes(currentAttributes, right)
} else { } else {
break break
} }
@ -201,8 +195,8 @@ const minimizeAttributeChanges = (left, right, currentAttributes, attributes) =>
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
* @return {ItemInsertionResult} * @return {ItemInsertionResult}
@ -219,7 +213,7 @@ const insertAttributes = (transaction, parent, left, right, currentAttributes, a
if (currentVal !== val) { if (currentVal !== val) {
// save negated attribute (set null if currentVal undefined) // save negated attribute (set null if currentVal undefined)
negatedAttributes.set(key, currentVal) negatedAttributes.set(key, currentVal)
left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val) left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, new ContentFormat(key, val))
left.integrate(transaction) left.integrate(transaction)
} }
} }
@ -229,8 +223,8 @@ const insertAttributes = (transaction, parent, left, right, currentAttributes, a
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {string} text * @param {string} text
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
@ -250,11 +244,8 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a
left = insertPos.left left = insertPos.left
right = insertPos.right right = insertPos.right
// insert content // insert content
if (text.constructor === String) { const content = text.constructor === String ? new ContentString(text) : new ContentEmbed(text)
left = new ItemString(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, text) left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, content)
} else {
left = new ItemEmbed(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, text)
}
left.integrate(transaction) left.integrate(transaction)
return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes) return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes)
} }
@ -262,8 +253,8 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractType<any>} parent * @param {AbstractType<any>} parent
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {number} length * @param {number} length
* @param {Object<string,any>} attributes * @param {Object<string,any>} attributes
@ -282,26 +273,22 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
// delete all formats with attributes[format.key] != null // delete all formats with attributes[format.key] != null
while (length > 0 && right !== null) { while (length > 0 && right !== null) {
if (right.deleted === false) { if (right.deleted === false) {
switch (right.constructor) { switch (right.content.constructor) {
case ItemFormat: case ContentFormat:
// @ts-ignore right is ItemFormat const { key, value } = /** @type {ContentFormat} */ (right.content)
const attr = attributes[right.key] const attr = attributes[key]
if (attr !== undefined) { if (attr !== undefined) {
// @ts-ignore right is ItemFormat if (attr === value) {
if (attr === right.value) { negatedAttributes.delete(key)
// @ts-ignore right is ItemFormat
negatedAttributes.delete(right.key)
} else { } else {
// @ts-ignore right is ItemFormat negatedAttributes.set(key, value)
negatedAttributes.set(right.key, right.value)
} }
right.delete(transaction) right.delete(transaction)
} }
// @ts-ignore right is ItemFormat updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (right.content))
updateCurrentAttributes(currentAttributes, right)
break break
case ItemEmbed: case ContentEmbed:
case ItemString: case ContentString:
if (length < right.length) { if (length < right.length) {
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length)) getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
} }
@ -317,8 +304,8 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {AbstractItem|null} left * @param {Item|null} left
* @param {AbstractItem|null} right * @param {Item|null} right
* @param {Map<string,any>} currentAttributes * @param {Map<string,any>} currentAttributes
* @param {number} length * @param {number} length
* @return {ItemListPosition} * @return {ItemListPosition}
@ -329,13 +316,12 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
const deleteText = (transaction, left, right, currentAttributes, length) => { const deleteText = (transaction, left, right, currentAttributes, length) => {
while (length > 0 && right !== null) { while (length > 0 && right !== null) {
if (right.deleted === false) { if (right.deleted === false) {
switch (right.constructor) { switch (right.content.constructor) {
case ItemFormat: case ContentFormat:
// @ts-ignore right is ItemFormat updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (right.content))
updateCurrentAttributes(currentAttributes, right)
break break
case ItemEmbed: case ContentEmbed:
case ItemString: case ContentString:
if (length < right.length) { if (length < right.length) {
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length)) getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
} }
@ -411,13 +397,10 @@ export class YTextEvent extends YEvent {
*/ */
get delta () { get delta () {
if (this._delta === null) { if (this._delta === null) {
const y = this.target.doc const y = /** @type {Doc} */ (this.target.doc)
// @ts-ignore this._delta = []
transact(y, transaction => { transact(y, transaction => {
/** const delta = /** @type {Array<DeltaItem>} */ (this._delta)
* @type {Array<DeltaItem>}
*/
const delta = []
const currentAttributes = new Map() // saves all current attributes for insert const currentAttributes = new Map() // saves all current attributes for insert
const oldAttributes = new Map() const oldAttributes = new Map()
let item = this.target._start let item = this.target._start
@ -432,7 +415,6 @@ export class YTextEvent extends YEvent {
let insert = '' let insert = ''
let retain = 0 let retain = 0
let deleteLen = 0 let deleteLen = 0
this._delta = delta
const addOp = () => { const addOp = () => {
if (action !== null) { if (action !== null) {
/** /**
@ -472,14 +454,13 @@ export class YTextEvent extends YEvent {
} }
} }
while (item !== null) { while (item !== null) {
switch (item.constructor) { switch (item.content.constructor) {
case ItemEmbed: case ContentEmbed:
if (this.adds(item)) { if (this.adds(item)) {
if (!this.deletes(item)) { if (!this.deletes(item)) {
addOp() addOp()
action = 'insert' action = 'insert'
// @ts-ignore item is ItemFormat insert = /** @type {ContentEmbed} */ (item.content).embed
insert = item.embed
addOp() addOp()
} }
} else if (this.deletes(item)) { } else if (this.deletes(item)) {
@ -496,15 +477,14 @@ export class YTextEvent extends YEvent {
retain += 1 retain += 1
} }
break break
case ItemString: case ContentString:
if (this.adds(item)) { if (this.adds(item)) {
if (!this.deletes(item)) { if (!this.deletes(item)) {
if (action !== 'insert') { if (action !== 'insert') {
addOp() addOp()
action = 'insert' action = 'insert'
} }
// @ts-ignore insert += /** @type {ContentString} */ (item.content).str
insert += item.string
} }
} else if (this.deletes(item)) { } else if (this.deletes(item)) {
if (action !== 'delete') { if (action !== 'delete') {
@ -520,59 +500,45 @@ export class YTextEvent extends YEvent {
retain += item.length retain += item.length
} }
break break
case ItemFormat: case ContentFormat:
const { key, value } = /** @type {ContentFormat} */ (item.content)
if (this.adds(item)) { if (this.adds(item)) {
if (!this.deletes(item)) { if (!this.deletes(item)) {
// @ts-ignore item is ItemFormat const curVal = currentAttributes.get(key) || null
const curVal = currentAttributes.get(item.key) || null if (curVal !== value) {
// @ts-ignore item is ItemFormat
if (curVal !== item.value) {
if (action === 'retain') { if (action === 'retain') {
addOp() addOp()
} }
// @ts-ignore item is ItemFormat if (value === (oldAttributes.get(key) || null)) {
if (item.value === (oldAttributes.get(item.key) || null)) { delete attributes[key]
// @ts-ignore item is ItemFormat
delete attributes[item.key]
} else { } else {
// @ts-ignore item is ItemFormat attributes[key] = value
attributes[item.key] = item.value
} }
} else { } else {
item.delete(transaction) item.delete(transaction)
} }
} }
} else if (this.deletes(item)) { } else if (this.deletes(item)) {
// @ts-ignore item is ItemFormat oldAttributes.set(key, value)
oldAttributes.set(item.key, item.value) const curVal = currentAttributes.get(key) || null
// @ts-ignore item is ItemFormat if (curVal !== value) {
const curVal = currentAttributes.get(item.key) || null
// @ts-ignore item is ItemFormat
if (curVal !== item.value) {
if (action === 'retain') { if (action === 'retain') {
addOp() addOp()
} }
// @ts-ignore item is ItemFormat attributes[key] = curVal
attributes[item.key] = curVal
} }
} else if (!item.deleted) { } else if (!item.deleted) {
// @ts-ignore item is ItemFormat oldAttributes.set(key, value)
oldAttributes.set(item.key, item.value) const attr = attributes[key]
// @ts-ignore item is ItemFormat
const attr = attributes[item.key]
if (attr !== undefined) { if (attr !== undefined) {
// @ts-ignore item is ItemFormat if (attr !== value) {
if (attr !== item.value) {
if (action === 'retain') { if (action === 'retain') {
addOp() addOp()
} }
// @ts-ignore item is ItemFormat if (value === null) {
if (item.value === null) { attributes[key] = value
// @ts-ignore item is ItemFormat
attributes[item.key] = item.value
} else { } else {
// @ts-ignore item is ItemFormat delete attributes[key]
delete attributes[item.key]
} }
} else { } else {
item.delete(transaction) item.delete(transaction)
@ -583,26 +549,24 @@ export class YTextEvent extends YEvent {
if (action === 'insert') { if (action === 'insert') {
addOp() addOp()
} }
// @ts-ignore item is ItemFormat updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (item.content))
updateCurrentAttributes(currentAttributes, item)
} }
break break
} }
item = item.right item = item.right
} }
addOp() addOp()
while (this._delta.length > 0) { while (delta.length > 0) {
let lastOp = this._delta[this._delta.length - 1] let lastOp = delta[delta.length - 1]
if (lastOp.retain !== undefined && lastOp.attributes === undefined) { if (lastOp.retain !== undefined && lastOp.attributes === undefined) {
// retain delta's if they don't assign attributes // retain delta's if they don't assign attributes
this._delta.pop() delta.pop()
} else { } else {
break break
} }
} }
}) })
} }
// @ts-ignore _delta is defined above
return this._delta return this._delta
} }
} }
@ -636,15 +600,14 @@ export class YText extends AbstractType {
/** /**
* @param {Doc} y * @param {Doc} y
* @param {ItemType} item * @param {Item} item
* *
* @private * @private
*/ */
_integrate (y, item) { _integrate (y, item) {
super._integrate(y, item) super._integrate(y, item)
try { try {
// @ts-ignore this._prelimContent is still defined /** @type {Array<function>} */ (this._pending).forEach(f => f())
this._pending.forEach(f => f())
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
@ -671,13 +634,12 @@ export class YText extends AbstractType {
toString () { toString () {
let str = '' let str = ''
/** /**
* @type {AbstractItem|null} * @type {Item|null}
*/ */
let n = this._start let n = this._start
while (n !== null) { while (n !== null) {
if (!n.deleted && n.countable && n.constructor === ItemString) { if (!n.deleted && n.countable && n.content.constructor === ContentString) {
// @ts-ignore str += /** @type {ContentString} */ (n.content).str
str += n.string
} }
n = n.right n = n.right
} }
@ -711,8 +673,7 @@ export class YText extends AbstractType {
} }
}) })
} else { } else {
// @ts-ignore /** @type {Array<function>} */ (this._pending).push(() => this.applyDelta(delta))
this._pending.push(() => this.applyDelta(delta))
} }
} }
@ -732,10 +693,6 @@ export class YText extends AbstractType {
const ops = [] const ops = []
const currentAttributes = new Map() const currentAttributes = new Map()
let str = '' let str = ''
/**
* @type {AbstractItem|null}
*/
// @ts-ignore
let n = this._start let n = this._start
function packStr () { function packStr () {
if (str.length > 0) { if (str.length > 0) {
@ -762,8 +719,8 @@ export class YText extends AbstractType {
} }
while (n !== null) { while (n !== null) {
if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) { if (isVisible(n, snapshot) || (prevSnapshot !== undefined && isVisible(n, prevSnapshot))) {
switch (n.constructor) { switch (n.content.constructor) {
case ItemString: case ContentString:
const cur = currentAttributes.get('ychange') const cur = currentAttributes.get('ychange')
if (snapshot !== undefined && !isVisible(n, snapshot)) { if (snapshot !== undefined && !isVisible(n, snapshot)) {
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') { if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') {
@ -779,20 +736,17 @@ export class YText extends AbstractType {
packStr() packStr()
currentAttributes.delete('ychange') currentAttributes.delete('ychange')
} }
// @ts-ignore str += /** @type {ContentString} */ (n.content).str
str += n.string
break break
case ItemEmbed: case ContentEmbed:
packStr() packStr()
ops.push({ ops.push({
// @ts-ignore item is ItemFormat insert: /** @type {ContentEmbed} */ (n.content).embed
insert: n.embed
}) })
break break
case ItemFormat: case ContentFormat:
packStr() packStr()
// @ts-ignore updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (n.content))
updateCurrentAttributes(currentAttributes, n)
break break
} }
} }
@ -823,8 +777,7 @@ export class YText extends AbstractType {
insertText(transaction, this, left, right, currentAttributes, text, attributes) insertText(transaction, this, left, right, currentAttributes, text, attributes)
}) })
} else { } else {
// @ts-ignore /** @type {Array<function>} */ (this._pending).push(() => this.insert(index, text, attributes))
this._pending.push(() => this.insert(index, text, attributes))
} }
} }
@ -849,8 +802,7 @@ export class YText extends AbstractType {
insertText(transaction, this, left, right, currentAttributes, embed, attributes) insertText(transaction, this, left, right, currentAttributes, embed, attributes)
}) })
} else { } else {
// @ts-ignore /** @type {Array<function>} */ (this._pending).push(() => this.insertEmbed(index, embed, attributes))
this._pending.push(() => this.insertEmbed(index, embed, attributes))
} }
} }
@ -873,8 +825,7 @@ export class YText extends AbstractType {
deleteText(transaction, left, right, currentAttributes, length) deleteText(transaction, left, right, currentAttributes, length)
}) })
} else { } else {
// @ts-ignore /** @type {Array<function>} */ (this._pending).push(() => this.delete(index, length))
this._pending.push(() => this.delete(index, length))
} }
} }
@ -899,8 +850,7 @@ export class YText extends AbstractType {
formatText(transaction, this, left, right, currentAttributes, length, attributes) formatText(transaction, this, left, right, currentAttributes, length, attributes)
}) })
} else { } else {
// @ts-ignore /** @type {Array<function>} */ (this._pending).push(() => this.format(index, length, attributes))
this._pending.push(() => this.format(index, length, attributes))
} }
} }

View File

@ -8,7 +8,7 @@ import {
typeMapGetAll, typeMapGetAll,
typeListForEach, typeListForEach,
YXmlElementRefID, YXmlElementRefID,
Snapshot, Doc, ItemType // eslint-disable-line Snapshot, Doc, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
@ -40,16 +40,14 @@ export class YXmlElement extends YXmlFragment {
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Doc} y The Yjs instance * @param {Doc} y The Yjs instance
* @param {ItemType} item * @param {Item} item
* @private * @private
*/ */
_integrate (y, item) { _integrate (y, item) {
super._integrate(y, item) super._integrate(y, item)
// @ts-ignore this.insert(0, /** @type {Array} */ (this._prelimContent))
this.insert(0, this._prelimContent)
this._prelimContent = null this._prelimContent = null
// @ts-ignore ;(/** @type {Map<string, any>} */ (this._prelimAttrs)).forEach((value, key) => {
this._prelimAttrs.forEach((value, key) => {
this.setAttribute(key, value) this.setAttribute(key, value)
}) })
this._prelimContent = null this._prelimContent = null
@ -105,8 +103,7 @@ export class YXmlElement extends YXmlFragment {
typeMapDelete(transaction, this, attributeName) typeMapDelete(transaction, this, attributeName)
}) })
} else { } else {
// @ts-ignore /** @type {Map<string,any>} */ (this._prelimAttrs).delete(attributeName)
this._prelimAttrs.delete(attributeName)
} }
} }
@ -124,8 +121,7 @@ export class YXmlElement extends YXmlFragment {
typeMapSet(transaction, this, attributeName, attributeValue) typeMapSet(transaction, this, attributeName, attributeValue)
}) })
} else { } else {
// @ts-ignore /** @type {Map<string, any>} */ (this._prelimAttrs).set(attributeName, attributeValue)
this._prelimAttrs.set(attributeName, attributeValue)
} }
} }
@ -139,8 +135,7 @@ export class YXmlElement extends YXmlFragment {
* @public * @public
*/ */
getAttribute (attributeName) { getAttribute (attributeName) {
// @ts-ignore return /** @type {any} */ (typeMapGet(this, attributeName))
return typeMapGet(this, attributeName)
} }
/** /**

View File

@ -14,7 +14,7 @@ import {
YXmlFragmentRefID, YXmlFragmentRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
@ -59,10 +59,9 @@ export class YXmlTreeWalker {
this._filter = f this._filter = f
this._root = root this._root = root
/** /**
* @type {ItemType | null} * @type {Item}
*/ */
// @ts-ignore this._currentNode = /** @type {Item} */ (root._start)
this._currentNode = root._start
this._firstCall = true this._firstCall = true
} }
@ -77,18 +76,21 @@ export class YXmlTreeWalker {
* @public * @public
*/ */
next () { next () {
/**
* @type {Item|null}
*/
let n = this._currentNode let n = this._currentNode
if (n !== null && (!this._firstCall || n.deleted || !this._filter(n.type))) { // if first call, we check if we can use the first item let type = /** @type {ContentType} */ (n.content).type
if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item
do { do {
if (!n.deleted && (n.type.constructor === YXmlElement || n.type.constructor === YXmlFragment) && n.type._start !== null) { type = /** @type {ContentType} */ (n.content).type
if (!n.deleted && (type.constructor === YXmlElement || type.constructor === YXmlFragment) && type._start !== null) {
// walk down in the tree // walk down in the tree
// @ts-ignore n = type._start
n = n.type._start
} else { } else {
// walk right or up in the tree // walk right or up in the tree
while (n !== null) { while (n !== null) {
if (n.right !== null) { if (n.right !== null) {
// @ts-ignore
n = n.right n = n.right
break break
} else if (n.parent === this._root) { } else if (n.parent === this._root) {
@ -98,16 +100,15 @@ export class YXmlTreeWalker {
} }
} }
} }
} while (n !== null && (n.deleted || !this._filter(n.type))) } while (n !== null && (n.deleted || !this._filter(/** @type {ContentType} */ (n.content).type)))
} }
this._firstCall = false this._firstCall = false
this._currentNode = n
if (n === null) { if (n === null) {
// @ts-ignore return undefined if done=true (the expected result) // @ts-ignore
return { value: undefined, done: true } return { value: undefined, done: true }
} }
// @ts-ignore this._currentNode = n
return { value: n.type, done: false } return { value: /** @type {any} */ (n.content).type, done: false }
} }
} }

View File

@ -3,7 +3,8 @@ import {
findIndexSS, findIndexSS,
createID, createID,
getState, getState,
AbstractStruct, AbstractItem, StructStore, Transaction, ID // eslint-disable-line splitItem,
Item, AbstractStruct, StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
@ -11,7 +12,7 @@ import * as map from 'lib0/map.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
class DeleteItem { export class DeleteItem {
/** /**
* @param {number} clock * @param {number} clock
* @param {number} len * @param {number} len
@ -235,13 +236,13 @@ export const readDeleteSet = (decoder, transaction, store) => {
let index = findIndexSS(structs, clock) let index = findIndexSS(structs, clock)
/** /**
* We can ignore the case of GC and Delete structs, because we are going to skip them * We can ignore the case of GC and Delete structs, because we are going to skip them
* @type {AbstractItem} * @type {Item}
*/ */
// @ts-ignore // @ts-ignore
let struct = structs[index] let struct = structs[index]
// split the first item if necessary // split the first item if necessary
if (!struct.deleted && struct.id.clock < clock) { if (!struct.deleted && struct.id.clock < clock) {
structs.splice(index + 1, 0, struct.splitAt(transaction, clock - struct.id.clock)) structs.splice(index + 1, 0, splitItem(transaction, struct, clock - struct.id.clock))
index++ // increase we now want to use the next struct index++ // increase we now want to use the next struct
} }
while (index < structs.length) { while (index < structs.length) {
@ -250,7 +251,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
if (struct.id.clock < clock + len) { if (struct.id.clock < clock + len) {
if (!struct.deleted) { if (!struct.deleted) {
if (clock + len < struct.id.clock + struct.length) { if (clock + len < struct.id.clock + struct.length) {
structs.splice(index, 0, struct.splitAt(transaction, clock + len - struct.id.clock)) structs.splice(index, 0, splitItem(transaction, struct, clock + len - struct.id.clock))
} }
struct.delete(transaction) struct.delete(transaction)
} }

View File

@ -10,7 +10,7 @@ import {
YMap, YMap,
YXmlFragment, YXmlFragment,
transact, transact,
AbstractItem, Transaction, YEvent // eslint-disable-line Item, Transaction, YEvent // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import { Observable } from 'lib0/observable.js' import { Observable } from 'lib0/observable.js'
@ -97,7 +97,7 @@ export class Doc extends Observable {
// @ts-ignore // @ts-ignore
const t = new TypeConstructor() const t = new TypeConstructor()
t._map = type._map t._map = type._map
type._map.forEach(/** @param {AbstractItem?} n */ n => { type._map.forEach(/** @param {Item?} n */ n => {
for (; n !== null; n = n.left) { for (; n !== null; n = n.left) {
n.parent = t n.parent = t
} }

View File

@ -1,16 +1,15 @@
import { import {
getItem, getItem,
getItemType,
createID, createID,
writeID, writeID,
readID, readID,
compareIDs, compareIDs,
getState, getState,
findRootTypeKey, findRootTypeKey,
AbstractItem, Item,
ItemType, ContentType,
ID, StructStore, Doc, AbstractType // eslint-disable-line ID, Doc, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
@ -224,7 +223,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
return null return null
} }
const right = getItem(store, rightID) const right = getItem(store, rightID)
if (!(right instanceof AbstractItem)) { if (!(right instanceof Item)) {
return null return null
} }
index = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock index = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock
@ -244,9 +243,9 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
// type does not exist yet // type does not exist yet
return null return null
} }
const struct = getItemType(store, typeID) const struct = getItem(store, typeID)
if (struct instanceof ItemType) { if (struct instanceof Item && struct.content instanceof ContentType) {
type = struct.type type = struct.content.type
} else { } else {
// struct is garbage collected // struct is garbage collected
return null return null

View File

@ -1,7 +1,7 @@
import { import {
isDeleted, isDeleted,
DeleteSet, AbstractItem // eslint-disable-line DeleteSet, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
export class Snapshot { export class Snapshot {
@ -31,7 +31,7 @@ export class Snapshot {
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm) export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
/** /**
* @param {AbstractItem} item * @param {Item} item
* @param {Snapshot|undefined} snapshot * @param {Snapshot|undefined} snapshot
* *
* @protected * @protected

View File

@ -1,7 +1,8 @@
import { import {
GC, GC,
Transaction, AbstractStructRef, ID, ItemType, AbstractItem, AbstractStruct // eslint-disable-line splitItem,
GCRef, ItemRef, Transaction, ID, Item, AbstractStruct // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
@ -21,14 +22,14 @@ export class StructStore {
* We could shift the array of refs instead, but shift is incredible * We could shift the array of refs instead, but shift is incredible
* slow in Chrome for arrays with more than 100k elements * slow in Chrome for arrays with more than 100k elements
* @see tryResumePendingStructRefs * @see tryResumePendingStructRefs
* @type {Map<number,{i:number,refs:Array<AbstractStructRef>}>} * @type {Map<number,{i:number,refs:Array<GCRef|ItemRef>}>}
* @private * @private
*/ */
this.pendingClientsStructRefs = new Map() this.pendingClientsStructRefs = new Map()
/** /**
* Stack of pending structs waiting for struct dependencies * Stack of pending structs waiting for struct dependencies
* Maximum length of stack is structReaders.size * Maximum length of stack is structReaders.size
* @type {Array<AbstractStructRef>} * @type {Array<GCRef|ItemRef>}
* @private * @private
*/ */
this.pendingStack = [] this.pendingStack = []
@ -169,7 +170,7 @@ export const find = (store, id) => {
* *
* @param {StructStore} store * @param {StructStore} store
* @param {ID} id * @param {ID} id
* @return {AbstractItem} * @return {Item}
* *
* @private * @private
* @function * @function
@ -177,43 +178,23 @@ export const find = (store, id) => {
// @ts-ignore // @ts-ignore
export const getItem = (store, id) => find(store, id) export const getItem = (store, id) => find(store, id)
/**
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
*
* @param {StructStore} store
* @param {ID} id
* @return {ItemType}
*
* @private
* @function
*/
// @ts-ignore
export const getItemType = (store, id) => find(store, id)
/** /**
* Expects that id is actually in store. This function throws or is an infinite loop otherwise. * Expects that id is actually in store. This function throws or is an infinite loop otherwise.
* *
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {ID} id * @param {ID} id
* @return {AbstractItem} * @return {Item}
* *
* @private * @private
* @function * @function
*/ */
export const getItemCleanStart = (transaction, store, id) => { export const getItemCleanStart = (transaction, store, id) => {
/** const structs = /** @type {Array<Item>} */ (store.clients.get(id.client))
* @type {Array<AbstractItem>}
*/
// @ts-ignore
const structs = store.clients.get(id.client)
const index = findIndexSS(structs, id.clock) const index = findIndexSS(structs, id.clock)
/**
* @type {AbstractItem}
*/
let struct = structs[index] let struct = structs[index]
if (struct.id.clock < id.clock && struct.constructor !== GC) { if (struct.id.clock < id.clock && struct.constructor !== GC) {
struct = struct.splitAt(transaction, id.clock - struct.id.clock) struct = splitItem(transaction, struct, id.clock - struct.id.clock)
structs.splice(index + 1, 0, struct) structs.splice(index + 1, 0, struct)
} }
return struct return struct
@ -225,21 +206,21 @@ export const getItemCleanStart = (transaction, store, id) => {
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* @param {ID} id * @param {ID} id
* @return {AbstractItem} * @return {Item}
* *
* @private * @private
* @function * @function
*/ */
export const getItemCleanEnd = (transaction, store, id) => { export const getItemCleanEnd = (transaction, store, id) => {
/** /**
* @type {Array<AbstractItem>} * @type {Array<Item>}
*/ */
// @ts-ignore // @ts-ignore
const structs = store.clients.get(id.client) const structs = store.clients.get(id.client)
const index = findIndexSS(structs, id.clock) const index = findIndexSS(structs, id.clock)
const struct = structs[index] const struct = structs[index]
if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) { if (id.clock !== struct.id.clock + struct.length - 1 && struct.constructor !== GC) {
structs.splice(index + 1, 0, struct.splitAt(transaction, id.clock - struct.id.clock + 1)) structs.splice(index + 1, 0, splitItem(transaction, struct, id.clock - struct.id.clock + 1))
} }
return struct return struct
} }
@ -254,10 +235,6 @@ export const getItemCleanEnd = (transaction, store, id) => {
* @function * @function
*/ */
export const replaceStruct = (store, struct, newStruct) => { export const replaceStruct = (store, struct, newStruct) => {
/** const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(struct.id.client))
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(struct.id.client)
structs[findIndexSS(structs, struct.id.clock)] = newStruct structs[findIndexSS(structs, struct.id.clock)] = newStruct
} }

View File

@ -9,7 +9,7 @@ import {
getStateVector, getStateVector,
findIndexSS, findIndexSS,
callEventHandlerListeners, callEventHandlerListeners,
AbstractItem, Item,
ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -179,20 +179,15 @@ export const transact = (doc, f, origin = null) => {
if (left.deleted === right.deleted && left.constructor === right.constructor) { if (left.deleted === right.deleted && left.constructor === right.constructor) {
if (left.mergeWith(right)) { if (left.mergeWith(right)) {
structs.splice(pos, 1) structs.splice(pos, 1)
if (right instanceof AbstractItem && right.parentSub !== null && right.parent._map.get(right.parentSub) === right) { if (right instanceof Item && right.parentSub !== null && right.parent._map.get(right.parentSub) === right) {
// @ts-ignore we already did a constructor check above right.parent._map.set(right.parentSub, /** @type {Item} */ (left))
right.parent._map.set(right.parentSub, left)
} }
} }
} }
} }
// replace deleted items with ItemDeleted / GC // replace deleted items with ItemDeleted / GC
for (const [client, deleteItems] of ds.clients) { for (const [client, deleteItems] of ds.clients) {
/** const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(client)
for (let di = deleteItems.length - 1; di >= 0; di--) { for (let di = deleteItems.length - 1; di >= 0; di--) {
const deleteItem = deleteItems[di] const deleteItem = deleteItems[di]
const endDeleteItemClock = deleteItem.clock + deleteItem.len const endDeleteItemClock = deleteItem.clock + deleteItem.len
@ -205,7 +200,7 @@ export const transact = (doc, f, origin = null) => {
if (deleteItem.clock + deleteItem.len <= struct.id.clock) { if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
break break
} }
if (struct.deleted && struct instanceof AbstractItem) { if (struct.deleted && struct instanceof Item) {
struct.gc(store, false) struct.gc(store, false)
} }
} }
@ -214,11 +209,7 @@ export const transact = (doc, f, origin = null) => {
// try to merge deleted / gc'd items // try to merge deleted / gc'd items
// merge from right to left for better efficiecy and so we don't miss any merge targets // merge from right to left for better efficiecy and so we don't miss any merge targets
for (const [client, deleteItems] of ds.clients) { for (const [client, deleteItems] of ds.clients) {
/** const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(client)
for (let di = deleteItems.length - 1; di >= 0; di--) { for (let di = deleteItems.length - 1; di >= 0; di--) {
const deleteItem = deleteItems[di] const deleteItem = deleteItems[di]
// start with merging the item next to the last deleted item // start with merging the item next to the last deleted item
@ -237,11 +228,7 @@ export const transact = (doc, f, origin = null) => {
for (const [client, clock] of transaction.afterState) { for (const [client, clock] of transaction.afterState) {
const beforeClock = transaction.beforeState.get(client) || 0 const beforeClock = transaction.beforeState.get(client) || 0
if (beforeClock !== clock) { if (beforeClock !== clock) {
/** const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(client)
// we iterate from right to left so we can safely remove entries // we iterate from right to left so we can safely remove entries
const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1) const firstChangePos = math.max(findIndexSS(structs, beforeClock), 1)
for (let i = structs.length - 1; i >= firstChangePos; i--) { for (let i = structs.length - 1; i >= firstChangePos; i--) {
@ -255,11 +242,7 @@ export const transact = (doc, f, origin = null) => {
for (const mid of transaction._mergeStructs) { for (const mid of transaction._mergeStructs) {
const client = mid.client const client = mid.client
const clock = mid.clock const clock = mid.clock
/** const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(client)
const replacedStructPos = findIndexSS(structs, clock) const replacedStructPos = findIndexSS(structs, clock)
if (replacedStructPos + 1 < structs.length) { if (replacedStructPos + 1 < structs.length) {
tryToMergeWithLeft(structs, replacedStructPos + 1) tryToMergeWithLeft(structs, replacedStructPos + 1)

View File

@ -200,4 +200,3 @@ export class UndoManager {
return performedRedo return performedRedo
} }
} }
}

View File

@ -1,18 +1,23 @@
/** /**
* @module encoding * @module encoding
*
* We use the first five bits in the info flag for determining the type of the struct.
*
* 0: GC
* 1: Item with Deleted content
* 2: Item with JSON content
* 3: Item with Binary content
* 4: Item with String content
* 5: Item with Embed content (for richtext content)
* 6: Item with Format content (a formatting marker for richtext content)
* 7: Item with Type
*/ */
import { import {
findIndexSS, findIndexSS,
GCRef, GCRef,
ItemBinaryRef, ItemRef,
ItemDeletedRef,
ItemEmbedRef,
ItemFormatRef,
ItemJSONRef,
ItemStringRef,
ItemTypeRef,
writeID, writeID,
createID, createID,
readID, readID,
@ -21,27 +26,13 @@ import {
readDeleteSet, readDeleteSet,
writeDeleteSet, writeDeleteSet,
createDeleteSetFromStructStore, createDeleteSetFromStructStore,
Doc, Transaction, AbstractStruct, AbstractStructRef, StructStore, ID // eslint-disable-line Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
import * as binary from 'lib0/binary.js' import * as binary from 'lib0/binary.js'
/**
* @private
*/
export const structRefs = [
GCRef,
ItemBinaryRef,
ItemDeletedRef,
ItemEmbedRef,
ItemFormatRef,
ItemJSONRef,
ItemStringRef,
ItemTypeRef
]
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {Array<AbstractStruct>} structs All structs by `client` * @param {Array<AbstractStruct>} structs All structs by `client`
@ -68,19 +59,19 @@ const writeStructs = (encoder, structs, client, clock) => {
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {number} numOfStructs * @param {number} numOfStructs
* @param {ID} nextID * @param {ID} nextID
* @return {Array<AbstractStructRef>} * @return {Array<GCRef|ItemRef>}
* *
* @private * @private
* @function * @function
*/ */
const readStructRefs = (decoder, numOfStructs, nextID) => { const readStructRefs = (decoder, numOfStructs, nextID) => {
/** /**
* @type {Array<AbstractStructRef>} * @type {Array<GCRef|ItemRef>}
*/ */
const refs = [] const refs = []
for (let i = 0; i < numOfStructs; i++) { for (let i = 0; i < numOfStructs; i++) {
const info = decoding.readUint8(decoder) const info = decoding.readUint8(decoder)
const ref = new structRefs[binary.BITS5 & info](decoder, nextID, info) const ref = (binary.BITS5 & info) === 0 ? new GCRef(decoder, nextID, info) : new ItemRef(decoder, nextID, info)
nextID = createID(nextID.client, nextID.clock + ref.length) nextID = createID(nextID.client, nextID.clock + ref.length)
refs.push(ref) refs.push(ref)
} }
@ -119,14 +110,14 @@ export const writeClientsStructs = (encoder, store, _sm) => {
/** /**
* @param {decoding.Decoder} decoder The decoder object to read data from. * @param {decoding.Decoder} decoder The decoder object to read data from.
* @return {Map<number,Array<AbstractStructRef>>} * @return {Map<number,Array<GCRef|ItemRef>>}
* *
* @private * @private
* @function * @function
*/ */
export const readClientsStructRefs = decoder => { export const readClientsStructRefs = decoder => {
/** /**
* @type {Map<number,Array<AbstractStructRef>>} * @type {Map<number,Array<GCRef|ItemRef>>}
*/ */
const clientRefs = new Map() const clientRefs = new Map()
const numOfStateUpdates = decoding.readVarUint(decoder) const numOfStateUpdates = decoding.readVarUint(decoder)
@ -254,7 +245,7 @@ export const writeStructsFromTransaction = (encoder, transaction) => writeClient
/** /**
* @param {StructStore} store * @param {StructStore} store
* @param {Map<number, Array<AbstractStructRef>>} clientsStructsRefs * @param {Map<number, Array<GCRef|ItemRef>>} clientsStructsRefs
* *
* @private * @private
* @function * @function

View File

@ -1,36 +1,26 @@
import * as t from 'lib0/testing.js' import * as t from 'lib0/testing.js'
import { import {
structRefs, contentRefs,
structGCRefNumber, readContentBinary,
structBinaryRefNumber, readContentDeleted,
structDeletedRefNumber, readContentString,
structEmbedRefNumber, readContentJSON,
structFormatRefNumber, readContentEmbed,
structJSONRefNumber, readContentType,
structStringRefNumber, readContentFormat
structTypeRefNumber,
GCRef,
ItemBinaryRef,
ItemDeletedRef,
ItemEmbedRef,
ItemFormatRef,
ItemJSONRef,
ItemStringRef,
ItemTypeRef
} from '../src/internals.js' } from '../src/internals.js'
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testStructReferences = tc => { export const testStructReferences = tc => {
t.assert(structRefs.length === 8) t.assert(contentRefs.length === 8)
t.assert(structRefs[structGCRefNumber] === GCRef) t.assert(contentRefs[1] === readContentDeleted)
t.assert(structRefs[structBinaryRefNumber] === ItemBinaryRef) t.assert(contentRefs[2] === readContentJSON)
t.assert(structRefs[structDeletedRefNumber] === ItemDeletedRef) t.assert(contentRefs[3] === readContentBinary)
t.assert(structRefs[structEmbedRefNumber] === ItemEmbedRef) t.assert(contentRefs[4] === readContentString)
t.assert(structRefs[structFormatRefNumber] === ItemFormatRef) t.assert(contentRefs[5] === readContentEmbed)
t.assert(structRefs[structJSONRefNumber] === ItemJSONRef) t.assert(contentRefs[6] === readContentFormat)
t.assert(structRefs[structStringRefNumber] === ItemStringRef) t.assert(contentRefs[7] === readContentType)
t.assert(structRefs[structTypeRefNumber] === ItemTypeRef)
} }

View File

@ -3,8 +3,8 @@ import * as Y from '../src/index.js'
import { import {
createDeleteSetFromStructStore, createDeleteSetFromStructStore,
getStateVector, getStateVector,
AbstractItem, Item,
DeleteSet, StructStore, Doc // eslint-disable-line DeleteItem, DeleteSet, StructStore, Doc // eslint-disable-line
} from '../src/internals.js' } from '../src/internals.js'
import * as t from 'lib0/testing.js' import * as t from 'lib0/testing.js'
@ -12,6 +12,7 @@ import * as prng from 'lib0/prng.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
import * as syncProtocol from 'y-protocols/sync.js' import * as syncProtocol from 'y-protocols/sync.js'
export * from '../src/internals.js'
/** /**
* @param {TestYInstance} y // publish message created by `y` to all other online clients * @param {TestYInstance} y // publish message created by `y` to all other online clients
@ -240,8 +241,7 @@ export const init = (tc, { users = 5 } = {}, initTestObject) => {
} }
testConnector.syncAll() testConnector.syncAll()
result.testObjects = result.users.map(initTestObject || (() => null)) result.testObjects = result.users.map(initTestObject || (() => null))
// @ts-ignore return /** @type {any} */ (result)
return result
} }
/** /**
@ -282,8 +282,7 @@ export const compare = users => {
t.compare(userArrayValues[i], userArrayValues[i + 1]) t.compare(userArrayValues[i], userArrayValues[i + 1])
t.compare(userMapValues[i], userMapValues[i + 1]) t.compare(userMapValues[i], userMapValues[i + 1])
t.compare(userXmlValues[i], userXmlValues[i + 1]) t.compare(userXmlValues[i], userXmlValues[i + 1])
// @ts-ignore t.compare(userTextValues[i].map(/** @param {any} a */ a => a.insert).join('').length, users[i].getText('text').length)
t.compare(userTextValues[i].map(a => a.insert).join('').length, users[i].getText('text').length)
t.compare(userTextValues[i], userTextValues[i + 1]) t.compare(userTextValues[i], userTextValues[i + 1])
t.compare(getStateVector(users[i].store), getStateVector(users[i + 1].store)) t.compare(getStateVector(users[i].store), getStateVector(users[i + 1].store))
compareDS(createDeleteSetFromStructStore(users[i].store), createDeleteSetFromStructStore(users[i + 1].store)) compareDS(createDeleteSetFromStructStore(users[i].store), createDeleteSetFromStructStore(users[i + 1].store))
@ -293,8 +292,8 @@ export const compare = users => {
} }
/** /**
* @param {AbstractItem?} a * @param {Item?} a
* @param {AbstractItem?} b * @param {Item?} b
* @return {boolean} * @return {boolean}
*/ */
export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id)) export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id))
@ -306,11 +305,10 @@ export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y
export const compareStructStores = (ss1, ss2) => { export const compareStructStores = (ss1, ss2) => {
t.assert(ss1.clients.size === ss2.clients.size) t.assert(ss1.clients.size === ss2.clients.size)
for (const [client, structs1] of ss1.clients) { for (const [client, structs1] of ss1.clients) {
const structs2 = ss2.clients.get(client) const structs2 = /** @type {Array<Y.AbstractStruct>} */ (ss2.clients.get(client))
t.assert(structs2 !== undefined && structs1.length === structs2.length) t.assert(structs2 !== undefined && structs1.length === structs2.length)
for (let i = 0; i < structs1.length; i++) { for (let i = 0; i < structs1.length; i++) {
const s1 = structs1[i] const s1 = structs1[i]
// @ts-ignore
const s2 = structs2[i] const s2 = structs2[i]
// checks for abstract struct // checks for abstract struct
if ( if (
@ -321,9 +319,9 @@ export const compareStructStores = (ss1, ss2) => {
) { ) {
t.fail('Structs dont match') t.fail('Structs dont match')
} }
if (s1 instanceof AbstractItem) { if (s1 instanceof Item) {
if ( if (
!(s2 instanceof AbstractItem) || !(s2 instanceof Item) ||
!((s1.left === null && s2.left === null) || (s1.left !== null && s2.left !== null && Y.compareIDs(s1.left.lastId, s2.left.lastId))) || !((s1.left === null && s2.left === null) || (s1.left !== null && s2.left !== null && Y.compareIDs(s1.left.lastId, s2.left.lastId))) ||
!compareItemIDs(s1.right, s2.right) || !compareItemIDs(s1.right, s2.right) ||
!Y.compareIDs(s1.origin, s2.origin) || !Y.compareIDs(s1.origin, s2.origin) ||
@ -349,11 +347,10 @@ export const compareStructStores = (ss1, ss2) => {
export const compareDS = (ds1, ds2) => { export const compareDS = (ds1, ds2) => {
t.assert(ds1.clients.size === ds2.clients.size) t.assert(ds1.clients.size === ds2.clients.size)
for (const [client, deleteItems1] of ds1.clients) { for (const [client, deleteItems1] of ds1.clients) {
const deleteItems2 = ds2.clients.get(client) const deleteItems2 = /** @type {Array<DeleteItem>} */ (ds2.clients.get(client))
t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length) t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length)
for (let i = 0; i < deleteItems1.length; i++) { for (let i = 0; i < deleteItems1.length; i++) {
const di1 = deleteItems1[i] const di1 = deleteItems1[i]
// @ts-ignore
const di2 = deleteItems2[i] const di2 = deleteItems2[i]
if (di1.clock !== di2.clock || di1.len !== di2.len) { if (di1.clock !== di2.clock || di1.len !== di2.len) {
t.fail('DeleteSets dont match') t.fail('DeleteSets dont match')

View File

@ -1,4 +1,4 @@
import { init, compare, applyRandomTests, TestYInstance } from './testHelper.js' // eslint-disable-line import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
import * as Y from '../src/index.js' import * as Y from '../src/index.js'
import * as t from 'lib0/testing.js' import * as t from 'lib0/testing.js'
@ -282,7 +282,7 @@ let _uniqueNumber = 0
const getUniqueNumber = () => _uniqueNumber++ const getUniqueNumber = () => _uniqueNumber++
/** /**
* @type {Array<function(TestYInstance,prng.PRNG):void>} * @type {Array<function(Doc,prng.PRNG,any):void>}
*/ */
const arrayTransactions = [ const arrayTransactions = [
function insert (user, gen) { function insert (user, gen) {

View File

@ -1,4 +1,4 @@
import { init, compare, applyRandomTests, TestYInstance } from './testHelper.js' // eslint-disable-line import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
import { import {
compareIDs compareIDs
@ -328,7 +328,7 @@ export const testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser = tc
} }
/** /**
* @type {Array<function(TestYInstance,prng.PRNG):void>} * @type {Array<function(Doc,prng.PRNG):void>}
*/ */
const mapTransactions = [ const mapTransactions = [
function set (user, gen) { function set (user, gen) {

View File

@ -63,7 +63,7 @@ export const testBasicFormat = tc => {
t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }]) t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }])
t.compare(delta, [{ insert: 'z', attributes: { bold: true } }]) t.compare(delta, [{ insert: 'z', attributes: { bold: true } }])
// @ts-ignore // @ts-ignore
t.assert(text0._start.right.right.right.string === 'b', 'Does not insert duplicate attribute marker') t.assert(text0._start.right.right.right.content.str === 'b', 'Does not insert duplicate attribute marker')
text0.insert(0, 'y') text0.insert(0, 'y')
t.assert(text0.toString() === 'yzb') t.assert(text0.toString() === 'yzb')
t.compare(text0.toDelta(), [{ insert: 'y' }, { insert: 'zb', attributes: { bold: true } }]) t.compare(text0.toDelta(), [{ insert: 'y' }, { insert: 'zb', attributes: { bold: true } }])
@ -79,11 +79,11 @@ export const testBasicFormat = tc => {
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testGetDeltaWithEmbeds = tc => { export const testGetDeltaWithEmbeds = tc => {
const { users, text0 } = init(tc, { users: 1 }) const { text0 } = init(tc, { users: 1 })
text0.applyDelta([{ text0.applyDelta([{
insert: {linebreak: "s"} insert: {linebreak: 's'}
}]) }])
t.compare(text0.toDelta(), [{ t.compare(text0.toDelta(), [{
insert: {linebreak: "s"} insert: {linebreak: 's'}
}]) }])
} }

View File

@ -36,8 +36,10 @@
/* Module Resolution Options */ /* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ "paths": {
"yjs": ["./src/index.js"]
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */