permission protocol + reduce circular dependencies

This commit is contained in:
Kevin Jahns 2018-12-11 19:49:21 +01:00
parent e09ef15349
commit 04066a5678
29 changed files with 380 additions and 213 deletions

View File

@ -9,10 +9,13 @@ Until [this](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, the
```json ```json
{ {
"checkJs": true, "compilerOptions": {
"allowJs": true,
"checkJs": true,
..
},
"include": [ "include": [
"./node_modules/yjs/" "./node_modules/yjs/"
] ]
..
} }
``` ```

View File

@ -94,7 +94,13 @@ export const cursorPlugin = new Plugin({
const decorations = [] const decorations = []
awareness.forEach((aw, userID) => { awareness.forEach((aw, userID) => {
if (aw.cursor != null) { if (aw.cursor != null) {
const username = `User: ${userID}` let user = aw.user || {}
if (user.color == null) {
user.color = '#ffa50070'
}
if (user.name == null) {
user.name = `User: ${userID}`
}
let anchor = relativePositionToAbsolutePosition(ystate.type, aw.cursor.anchor || null, ystate.binding.mapping) let anchor = relativePositionToAbsolutePosition(ystate.type, aw.cursor.anchor || null, ystate.binding.mapping)
let head = relativePositionToAbsolutePosition(ystate.type, aw.cursor.head || null, ystate.binding.mapping) let head = relativePositionToAbsolutePosition(ystate.type, aw.cursor.head || null, ystate.binding.mapping)
if (anchor !== null && head !== null) { if (anchor !== null && head !== null) {
@ -104,14 +110,16 @@ export const cursorPlugin = new Plugin({
decorations.push(Decoration.widget(head, () => { decorations.push(Decoration.widget(head, () => {
const cursor = document.createElement('span') const cursor = document.createElement('span')
cursor.classList.add('ProseMirror-yjs-cursor') cursor.classList.add('ProseMirror-yjs-cursor')
const user = document.createElement('div') cursor.setAttribute('style', `border-color: ${user.color}`)
user.insertBefore(document.createTextNode(username), null) const userDiv = document.createElement('div')
cursor.insertBefore(user, null) userDiv.setAttribute('style', `background-color: ${user.color}`)
userDiv.insertBefore(document.createTextNode(user.name), null)
cursor.insertBefore(userDiv, null)
return cursor return cursor
}, { key: username })) }, { key: userID + '' }))
const from = math.min(anchor, head) const from = math.min(anchor, head)
const to = math.max(anchor, head) const to = math.max(anchor, head)
decorations.push(Decoration.inline(from, to, { style: 'background-color: #ffa50070' })) decorations.push(Decoration.inline(from, to, { style: `background-color: ${user.color}` }))
} }
} }
}) })
@ -429,7 +437,10 @@ export const createTypeFromNode = (node, mapping) => {
} else { } else {
type = new YXmlElement(node.type.name) type = new YXmlElement(node.type.name)
for (let key in node.attrs) { for (let key in node.attrs) {
type.setAttribute(key, node.attrs[key]) const val = node.attrs[key]
if (val !== null) {
type.setAttribute(key, val)
}
} }
const ins = [] const ins = []
for (let i = 0; i < node.childCount; i++) { for (let i = 0; i < node.childCount; i++) {
@ -441,15 +452,25 @@ export const createTypeFromNode = (node, mapping) => {
return type return type
} }
const equalAttrs = (pattrs, yattrs) => {
const keys = Object.keys(pattrs).filter(key => pattrs[key] === null)
let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] === null).length
for (let i = 0; i < keys.length && eq; i++) {
const key = keys[i]
eq = pattrs[key] === yattrs[key]
}
return eq
}
const equalYTextPText = (ytext, ptext) => { const equalYTextPText = (ytext, ptext) => {
const d = ytext.toDelta()[0] const d = ytext.toDelta()[0]
return d.insert === ptext.text && object.keys(d.attributes || {}).length === ptext.marks.length && ptext.marks.every(mark => object.equalFlat(d.attributes[mark.type.name], mark.attrs)) return d.insert === ptext.text && object.keys(d.attributes || {}).length === ptext.marks.length && ptext.marks.every(mark => equalAttrs(d.attributes[mark.type.name], mark.attrs))
} }
const equalYTypePNode = (ytype, pnode) => const equalYTypePNode = (ytype, pnode) =>
ytype.constructor === YText ytype.constructor === YText
? equalYTextPText(ytype, pnode) ? equalYTextPText(ytype, pnode)
: (matchNodeName(ytype, pnode) && ytype.length === pnode.childCount && object.equalFlat(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, pnode.child(i)))) : (matchNodeName(ytype, pnode) && ytype.length === pnode.childCount && equalAttrs(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, pnode.child(i))))
const computeChildEqualityFactor = (ytype, pnode, mapping) => { const computeChildEqualityFactor = (ytype, pnode, mapping) => {
const yChildren = ytype.toArray() const yChildren = ytype.toArray()
@ -497,13 +518,19 @@ const updateYFragment = (yDomFragment, pContent, mapping) => {
// update attributes // update attributes
if (yDomFragment instanceof YXmlElement) { if (yDomFragment instanceof YXmlElement) {
const yDomAttrs = yDomFragment.getAttributes() const yDomAttrs = yDomFragment.getAttributes()
for (let key in pContent.attrs) { const pAttrs = pContent.attrs
if (yDomAttrs[key] !== pContent.attrs[key]) { for (let key in pAttrs) {
yDomFragment.setAttribute(key, pContent.attrs[key]) if (pAttrs[key] !== null) {
if (yDomAttrs[key] !== pAttrs[key]) {
yDomFragment.setAttribute(key, pAttrs[key])
}
} else {
yDomFragment.removeAttribute(key)
} }
} }
// remove all keys that are no longer in pAttrs
for (let key in yDomAttrs) { for (let key in yDomAttrs) {
if (yDomAttrs[key] === undefined) { if (pAttrs[key] === undefined) {
yDomFragment.removeAttribute(key) yDomFragment.removeAttribute(key)
} }
} }

View File

@ -1,4 +1,5 @@
import './structs/Item.js'
import { Delete } from './structs/Delete.js' import { Delete } from './structs/Delete.js'
import { ItemJSON } from './structs/ItemJSON.js' import { ItemJSON } from './structs/ItemJSON.js'
import { ItemString } from './structs/ItemString.js' import { ItemString } from './structs/ItemString.js'
@ -15,6 +16,14 @@ import { YXmlElement, YXmlFragment } from './types/YXmlElement.js'
import { registerStruct } from './utils/structReferences.js' import { registerStruct } from './utils/structReferences.js'
import * as decoding from './lib/decoding.js'
import * as encoding from './lib/encoding.js'
import * as awarenessProtocol from './protocols/awareness.js'
import * as syncProtocol from './protocols/sync.js'
import * as authProtocol from './protocols/auth.js'
export { decoding, encoding, awarenessProtocol, syncProtocol, authProtocol }
export { Y } from './utils/Y.js' export { Y } from './utils/Y.js'
export { UndoManager } from './utils/UndoManager.js' export { UndoManager } from './utils/UndoManager.js'
export { Transaction } from './utils/Transaction.js' export { Transaction } from './utils/Transaction.js'
@ -28,10 +37,6 @@ export { YXmlElement as XmlElement, YXmlFragment as XmlFragment } from './types/
export { getRelativePosition, fromRelativePosition } from './utils/relativePosition.js' export { getRelativePosition, fromRelativePosition } from './utils/relativePosition.js'
export { registerStruct } from './utils/structReferences.js' export { registerStruct } from './utils/structReferences.js'
export * from './protocols/syncProtocol.js'
export * from './protocols/awarenessProtocol.js'
export * from './lib/encoding.js'
export * from './lib/decoding.js'
export * from './lib/mutex.js' export * from './lib/mutex.js'
registerStruct(0, GC) registerStruct(0, GC)

View File

@ -148,6 +148,21 @@ export const readVarUint = decoder => {
} }
} }
/**
* Look ahead and read varUint without incrementing position
*
* @function
* @param {Decoder} decoder
* @return {number}
*/
export const peekVarUint = decoder => {
let pos = decoder.pos
let s = readVarUint(decoder)
decoder.pos = pos
return s
}
/** /**
* Read string of variable length * Read string of variable length
* * varUint is used to store the length of the string * * varUint is used to store the length of the string
@ -189,3 +204,4 @@ export const peekVarString = decoder => {
decoder.pos = pos decoder.pos = pos
return s return s
} }

46
package-lock.json generated
View File

@ -67,12 +67,28 @@
"integrity": "sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA==", "integrity": "sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA==",
"dev": true "dev": true
}, },
"@types/events": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==",
"dev": true
},
"@types/node": { "@types/node": {
"version": "6.0.110", "version": "6.0.110",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.110.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.110.tgz",
"integrity": "sha512-LiaH3mF+OAqR+9Wo1OTJDbZDtCewAVjTbMhF1ZgUJ3fc8xqOJq6VqbpBh9dJVCVzByGmYIg2fREbuXNX0TKiJA==", "integrity": "sha512-LiaH3mF+OAqR+9Wo1OTJDbZDtCewAVjTbMhF1ZgUJ3fc8xqOJq6VqbpBh9dJVCVzByGmYIg2fREbuXNX0TKiJA==",
"dev": true "dev": true
}, },
"@types/ws": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz",
"integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==",
"dev": true,
"requires": {
"@types/events": "*",
"@types/node": "*"
}
},
"abab": { "abab": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
@ -3413,14 +3429,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -3435,20 +3449,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -3565,8 +3576,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -3578,7 +3588,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -3593,7 +3602,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -3601,14 +3609,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -3627,7 +3633,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -3708,8 +3713,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -3721,7 +3725,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -3843,7 +3846,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -5958,7 +5960,7 @@
}, },
"quill": { "quill": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "http://registry.npmjs.org/quill/-/quill-1.3.6.tgz", "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
"integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==", "integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@ -66,6 +66,7 @@
}, },
"homepage": "http://y-js.org", "homepage": "http://y-js.org",
"devDependencies": { "devDependencies": {
"@types/ws": "^6.0.1",
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-regenerator": "^6.26.0", "babel-plugin-transform-regenerator": "^6.26.0",

33
protocols/auth.js Normal file
View File

@ -0,0 +1,33 @@
import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js';
export const messagePermissionDenied = 0
/**
* @param {encoding.Encoder} encoder
* @param {string} reason
*/
export const writePermissionDenied = (encoder, reason) => {
encoding.writeVarUint(encoder, messagePermissionDenied)
encoding.writeVarString(encoder, reason)
}
/**
* @callback PermissionDeniedHandler
* @param {any} y
* @param {string} reason
*/
/**
*
* @param {decoding.Decoder} decoder
* @param {Y} y
* @param {PermissionDeniedHandler} permissionDeniedHandler
*/
export const readAuthMessage = (decoder, y, permissionDeniedHandler) => {
switch (decoding.readVarUint(decoder)) {
case messagePermissionDenied: permissionDeniedHandler(y, decoding.readVarString(decoder))
}
}

View File

@ -11,7 +11,7 @@ const messageUsersStateChanged = 0
/** /**
* @typedef {Object} UserStateUpdate * @typedef {Object} UserStateUpdate
* @property {number} UserStateUpdate.userID * @property {number} UserStateUpdate.userID
* @property {Object} state * @property {Object} UserStateUpdate.state
*/ */
/** /**
@ -91,13 +91,22 @@ export const readAwarenessMessage = (decoder, y) => {
} }
} }
/**
* @typedef {Object} UserState
* @property {number} UserState.userID
* @property {any} UserState.state
*/
/** /**
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @return {Array<UserState>} Array of state updates
*/ */
export const forwardAwarenessMessage = (decoder, encoder) => { export const forwardAwarenessMessage = (decoder, encoder) => {
let s = []
switch (decoding.readVarUint(decoder)) { switch (decoding.readVarUint(decoder)) {
case messageUsersStateChanged: case messageUsersStateChanged:
return forwardUsersStateChange(decoder, encoder) s = forwardUsersStateChange(decoder, encoder)
} }
return s
} }

View File

@ -10,6 +10,7 @@ import { deleteItemRange } from '../utils/structManipulation.js'
import { integrateRemoteStruct } from '../utils/integrateRemoteStructs.js' import { integrateRemoteStruct } from '../utils/integrateRemoteStructs.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
import { Item } from '../structs/Item.js' import { Item } from '../structs/Item.js'
import * as stringify from '../utils/structStringify.js'
/** /**
* @typedef {Map<number, number>} StateSet * @typedef {Map<number, number>} StateSet
@ -40,9 +41,9 @@ import { Item } from '../structs/Item.js'
* stringify[messageType] stringifies a message definition (messageType is already read from the bufffer) * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer)
*/ */
const messageYjsSyncStep1 = 0 export const messageYjsSyncStep1 = 0
const messageYjsSyncStep2 = 1 export const messageYjsSyncStep2 = 1
const messageYjsUpdate = 2 export const messageYjsUpdate = 2
/** /**
* Stringifies a message-encoded Delete Set. * Stringifies a message-encoded Delete Set.
@ -234,50 +235,6 @@ export const readStateSet = decoder => {
return ss return ss
} }
/**
* Stringify an item id.
*
* @param {ID.ID | ID.RootID} id
* @return {string}
*/
export const stringifyID = id => id instanceof ID.ID ? `(${id.user},${id.clock})` : `(${id.name},${id.type})`
/**
* Stringify an item as ID. HHere, an item could also be a Yjs instance (e.g. item._parent).
*
* @param {Item | Y | null} item
* @return {string}
*/
export const stringifyItemID = item => {
let result
if (item === null) {
result = '()'
} else if (item instanceof Item) {
result = stringifyID(item._id)
} else {
// must be a Yjs instance
// Don't include Y in this module, so we prevent circular dependencies.
result = 'y'
}
return result
}
/**
* Helper utility to convert an item to a readable format.
*
* @param {String} name The name of the item class (YText, ItemString, ..).
* @param {Item} item The item instance.
* @param {String} [append] Additional information to append to the returned
* string.
* @return {String} A readable string that represents the item object.
*
*/
export const logItemHelper = (name, item, append) => {
const left = item._left !== null ? stringifyID(item._left._lastId) : '()'
const origin = item._origin !== null ? stringifyID(item._origin._lastId) : '()'
return `${name}(id:${stringifyItemID(item)},left:${left},origin:${origin},right:${stringifyItemID(item._right)},parent:${stringifyItemID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
}
/** /**
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {Y} y * @param {Y} y
@ -293,7 +250,7 @@ export const stringifyStructs = (decoder, y) => {
let missing = struct._fromBinary(y, decoder) let missing = struct._fromBinary(y, decoder)
let logMessage = ' ' + struct._logString() let logMessage = ' ' + struct._logString()
if (missing.length > 0) { if (missing.length > 0) {
logMessage += ' .. missing: ' + missing.map(stringifyItemID).join(', ') logMessage += ' .. missing: ' + missing.map(stringify.stringifyItemID).join(', ')
} }
str += logMessage + '\n' str += logMessage + '\n'
} }
@ -410,10 +367,9 @@ export const stringifySyncStep2 = (decoder, y) => {
* Read and apply Structs and then DeleteSet to a y instance. * Read and apply Structs and then DeleteSet to a y instance.
* *
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {encoding.Encoder} encoder
* @param {Y} y * @param {Y} y
*/ */
export const readSyncStep2 = (decoder, encoder, y) => { export const readSyncStep2 = (decoder, y) => {
readStructs(decoder, y) readStructs(decoder, y)
readDeleteSet(decoder, y) readDeleteSet(decoder, y)
} }
@ -480,7 +436,7 @@ export const readSyncMessage = (decoder, encoder, y) => {
readSyncStep1(decoder, encoder, y) readSyncStep1(decoder, encoder, y)
break break
case messageYjsSyncStep2: case messageYjsSyncStep2:
y.transact(() => readSyncStep2(decoder, encoder, y), true) y.transact(() => readSyncStep2(decoder, y), true)
break break
case messageYjsUpdate: case messageYjsUpdate:
y.transact(() => readUpdate(decoder, y), true) y.transact(() => readUpdate(decoder, y), true)

View File

@ -9,28 +9,37 @@ import * as bc from '../../lib/broadcastchannel.js'
const messageSync = 0 const messageSync = 0
const messageAwareness = 1 const messageAwareness = 1
const messageAuth = 2
const reconnectTimeout = 3000 const reconnectTimeout = 3000
/**
* @param {WebsocketsSharedDocument} doc
* @param {string} reason
*/
const permissionDeniedHandler = (doc, reason) => console.warn(`Permission denied to access ${doc.url}.\n${reason}`)
/** /**
* @param {WebsocketsSharedDocument} doc * @param {WebsocketsSharedDocument} doc
* @param {ArrayBuffer} buf * @param {ArrayBuffer} buf
* @return {Y.Encoder} * @return {Y.encoding.Encoder}
*/ */
const readMessage = (doc, buf) => { const readMessage = (doc, buf) => {
const decoder = Y.createDecoder(buf) const decoder = Y.decoding.createDecoder(buf)
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
const messageType = Y.readVarUint(decoder) const messageType = Y.decoding.readVarUint(decoder)
switch (messageType) { switch (messageType) {
case messageSync: case messageSync:
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
doc.mux(() => doc.mux(() =>
Y.readSyncMessage(decoder, encoder, doc) Y.syncProtocol.readSyncMessage(decoder, encoder, doc)
) )
break break
case messageAwareness: case messageAwareness:
Y.readAwarenessMessage(decoder, doc) Y.awarenessProtocol.readAwarenessMessage(decoder, doc)
break break
case messageAuth:
Y.authProtocol.readAuthMessage(decoder, doc, permissionDeniedHandler)
} }
return encoder return encoder
} }
@ -41,8 +50,8 @@ const setupWS = (doc, url) => {
doc.ws = websocket doc.ws = websocket
websocket.onmessage = event => { websocket.onmessage = event => {
const encoder = readMessage(doc, event.data) const encoder = readMessage(doc, event.data)
if (Y.length(encoder) > 1) { if (Y.encoding.length(encoder) > 1) {
websocket.send(Y.toBuffer(encoder)) websocket.send(Y.encoding.toBuffer(encoder))
} }
} }
websocket.onclose = () => { websocket.onclose = () => {
@ -59,10 +68,10 @@ const setupWS = (doc, url) => {
status: 'disconnected' status: 'disconnected'
}) })
// always send sync step 1 when connected // always send sync step 1 when connected
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.writeSyncStep1(encoder, doc) Y.syncProtocol.writeSyncStep1(encoder, doc)
websocket.send(Y.toBuffer(encoder)) websocket.send(Y.encoding.toBuffer(encoder))
// force send stored awareness info // force send stored awareness info
doc.setAwarenessField(null, null) doc.setAwarenessField(null, null)
} }
@ -71,10 +80,10 @@ const setupWS = (doc, url) => {
const broadcastUpdate = (y, transaction) => { const broadcastUpdate = (y, transaction) => {
if (transaction.encodedStructsLen > 0) { if (transaction.encodedStructsLen > 0) {
y.mux(() => { y.mux(() => {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
const buf = Y.toBuffer(encoder) const buf = Y.encoding.toBuffer(encoder)
if (y.wsconnected) { if (y.wsconnected) {
y.ws.send(buf) y.ws.send(buf)
} }
@ -95,20 +104,20 @@ class WebsocketsSharedDocument extends Y.Y {
setupWS(this, url) setupWS(this, url)
this.on('afterTransaction', broadcastUpdate) this.on('afterTransaction', broadcastUpdate)
this._bcSubscriber = data => { this._bcSubscriber = data => {
const encoder = readMessage(this, data) this.mux(() => {
if (Y.length(encoder) > 1) { const encoder = readMessage(this, data)
this.mux(() => { if (Y.encoding.length(encoder) > 1) {
bc.publish(url, Y.toBuffer(encoder)) bc.publish(url, Y.encoding.toBuffer(encoder))
}) }
} })
} }
bc.subscribe(url, this._bcSubscriber) bc.subscribe(url, this._bcSubscriber)
// send sync step1 to bc // send sync step1 to bc
this.mux(() => { this.mux(() => {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.writeSyncStep1(encoder, this) Y.syncProtocol.writeSyncStep1(encoder, this)
bc.publish(url, Y.toBuffer(encoder)) bc.publish(url, Y.encoding.toBuffer(encoder))
}) })
} }
getLocalAwarenessInfo () { getLocalAwarenessInfo () {
@ -122,10 +131,10 @@ class WebsocketsSharedDocument extends Y.Y {
this._localAwarenessState[field] = value this._localAwarenessState[field] = value
} }
if (this.wsconnected) { if (this.wsconnected) {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageAwareness) Y.encoding.writeVarUint(encoder, messageAwareness)
Y.writeUsersStateChange(encoder, [{ userID: this.userID, state: this._localAwarenessState }]) Y.awarenessProtocol.writeUsersStateChange(encoder, [{ userID: this.userID, state: this._localAwarenessState }])
const buf = Y.toBuffer(encoder) const buf = Y.encoding.toBuffer(encoder)
this.ws.send(buf) this.ws.send(buf)
} }
} }

View File

@ -19,13 +19,14 @@ const docs = new Map()
const messageSync = 0 const messageSync = 0
const messageAwareness = 1 const messageAwareness = 1
const messageAuth = 2
const afterTransaction = (doc, transaction) => { const afterTransaction = (doc, transaction) => {
if (transaction.encodedStructsLen > 0) { if (transaction.encodedStructsLen > 0) {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
const message = Y.toBuffer(encoder) const message = Y.encoding.toBuffer(encoder)
doc.conns.forEach((_, conn) => conn.send(message)) doc.conns.forEach((_, conn) => conn.send(message))
} }
} }
@ -45,25 +46,25 @@ class WSSharedDoc extends Y.Y {
} }
const messageListener = (conn, doc, message) => { const messageListener = (conn, doc, message) => {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
const decoder = Y.createDecoder(message) const decoder = Y.decoding.createDecoder(message)
const messageType = Y.readVarUint(decoder) const messageType = Y.decoding.readVarUint(decoder)
switch (messageType) { switch (messageType) {
case messageSync: case messageSync:
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.readSyncMessage(decoder, encoder, doc) Y.syncProtocol.readSyncMessage(decoder, encoder, doc)
if (Y.length(encoder) > 1) { if (Y.encoding.length(encoder) > 1) {
conn.send(Y.toBuffer(encoder)) conn.send(Y.encoding.toBuffer(encoder))
} }
break break
case messageAwareness: { case messageAwareness: {
Y.writeVarUint(encoder, messageAwareness) Y.encoding.writeVarUint(encoder, messageAwareness)
const updates = Y.forwardAwarenessMessage(decoder, encoder) const updates = Y.awarenessProtocol.forwardAwarenessMessage(decoder, encoder)
updates.forEach(update => { updates.forEach(update => {
doc.awareness.set(update.userID, update.state) doc.awareness.set(update.userID, update.state)
doc.conns.get(conn).add(update.userID) doc.conns.get(conn).add(update.userID)
}) })
const buff = Y.toBuffer(encoder) const buff = Y.encoding.toBuffer(encoder)
doc.conns.forEach((_, c) => { doc.conns.forEach((_, c) => {
c.send(buff) c.send(buff)
}) })
@ -86,29 +87,29 @@ const setupConnection = (conn, req) => {
conn.on('close', () => { conn.on('close', () => {
const controlledIds = doc.conns.get(conn) const controlledIds = doc.conns.get(conn)
doc.conns.delete(conn) doc.conns.delete(conn)
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageAwareness) Y.encoding.writeVarUint(encoder, messageAwareness)
Y.writeUsersStateChange(encoder, Array.from(controlledIds).map(userID => { Y.awarenessProtocol.writeUsersStateChange(encoder, Array.from(controlledIds).map(userID => {
doc.awareness.delete(userID) doc.awareness.delete(userID)
return { userID, state: null } return { userID, state: null }
})) }))
const buf = Y.toBuffer(encoder) const buf = Y.encoding.toBuffer(encoder)
doc.conns.forEach((_, conn) => conn.send(buf)) doc.conns.forEach((_, conn) => conn.send(buf))
}) })
// send sync step 1 // send sync step 1
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
Y.writeVarUint(encoder, messageSync) Y.encoding.writeVarUint(encoder, messageSync)
Y.writeSyncStep1(encoder, doc) Y.syncProtocol.writeSyncStep1(encoder, doc)
conn.send(Y.toBuffer(encoder)) conn.send(Y.encoding.toBuffer(encoder))
if (doc.awareness.size > 0) { if (doc.awareness.size > 0) {
const encoder = Y.createEncoder() const encoder = Y.encoding.createEncoder()
const userStates = [] const userStates = []
doc.awareness.forEach((state, userID) => { doc.awareness.forEach((state, userID) => {
userStates.push({ state, userID }) userStates.push({ state, userID })
}) })
Y.writeVarUint(encoder, messageAwareness) Y.encoding.writeVarUint(encoder, messageAwareness)
Y.writeUsersStateChange(encoder, userStates) Y.awarenessProtocol.writeUsersStateChange(encoder, userStates)
conn.send(Y.toBuffer(encoder)) conn.send(Y.encoding.toBuffer(encoder))
} }
} }

View File

@ -4,13 +4,13 @@
import { getStructReference } from '../utils/structReferences.js' import { getStructReference } from '../utils/structReferences.js'
import * as ID from '../utils/ID.js' import * as ID from '../utils/ID.js'
import { stringifyID } from '../protocols/syncProtocol.js' import { writeStructToTransaction } from '../utils/structEncoding.js'
import { writeStructToTransaction } from '../utils/Transaction.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import { Item } from './Item.js' // eslint-disable-line // import { Item } from './Item.js' // eslint-disable-line
import { Y } from '../utils/Y.js' // eslint-disable-line // import { Y } from '../utils/Y.js' // eslint-disable-line
import { deleteItemRange } from '../utils/structManipulation.js' import { deleteItemRange } from '../utils/structManipulation.js'
import * as stringify from '../utils/structStringify.js'
/** /**
* @private * @private
@ -99,6 +99,6 @@ export class Delete {
* @private * @private
*/ */
_logString () { _logString () {
return `Delete - target: ${stringifyID(this._targetID)}, len: ${this._length}` return `Delete - target: ${stringify.stringifyID(this._targetID)}, len: ${this._length}`
} }
} }

View File

@ -4,10 +4,10 @@
import { getStructReference } from '../utils/structReferences.js' import { getStructReference } from '../utils/structReferences.js'
import * as ID from '../utils/ID.js' import * as ID from '../utils/ID.js'
import { writeStructToTransaction } from '../utils/Transaction.js' import { writeStructToTransaction } from '../utils/structEncoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line // import { Y } from '../utils/Y.js' // eslint-disable-line
// TODO should have the same base class as Item // TODO should have the same base class as Item
export class GC { export class GC {

View File

@ -5,12 +5,27 @@
import { getStructReference } from '../utils/structReferences.js' import { getStructReference } from '../utils/structReferences.js'
import * as ID from '../utils/ID.js' import * as ID from '../utils/ID.js'
import { Delete } from './Delete.js' import { Delete } from './Delete.js'
import { transactionTypeChanged, writeStructToTransaction } from '../utils/Transaction.js' import { writeStructToTransaction } from '../utils/structEncoding.js'
import { GC } from './GC.js' import { GC } from './GC.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js' // import { Type } from './Type.js' // eslint-disable-line
import { Type } from './Type.js' // eslint-disable-line
/**
* @private
*/
export const transactionTypeChanged = (y, type, sub) => {
if (type !== y && !type._deleted && !y._transaction.newTypes.has(type)) {
const changedTypes = y._transaction.changedTypes
let subs = changedTypes.get(type)
if (subs === undefined) {
// create if it doesn't exist yet
subs = new Set()
changedTypes.set(type, subs)
}
subs.add(sub)
}
}
/** /**
* @private * @private
@ -159,7 +174,7 @@ export class Item {
if (this._redone !== null) { if (this._redone !== null) {
return this._redone return this._redone
} }
if (this._parent instanceof Y) { if (!(this._parent instanceof Item)) {
return return
} }
let struct = this._copy() let struct = this._copy()

View File

@ -3,7 +3,7 @@
*/ */
import { Item } from './Item.js' import { Item } from './Item.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
@ -44,6 +44,6 @@ export class ItemEmbed extends Item {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('ItemEmbed', this, `embed:${JSON.stringify(this.embed)}`) return stringify.logItemHelper('ItemEmbed', this, `embed:${JSON.stringify(this.embed)}`)
} }
} }

View File

@ -3,7 +3,7 @@
*/ */
import { Item } from './Item.js' import { Item } from './Item.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
@ -51,6 +51,6 @@ export class ItemFormat extends Item {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('ItemFormat', this, `key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)}`) return stringify.logItemHelper('ItemFormat', this, `key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)}`)
} }
} }

View File

@ -3,7 +3,7 @@
*/ */
import { Item, splitHelper } from './Item.js' import { Item, splitHelper } from './Item.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
@ -19,7 +19,8 @@ export class ItemJSON extends Item {
return struct return struct
} }
get _length () { get _length () {
return this._content.length const c = this._content
return c !== null ? c.length : 0
} }
/** /**
* @param {Y} y * @param {Y} y
@ -46,11 +47,11 @@ export class ItemJSON extends Item {
*/ */
_toBinary (encoder) { _toBinary (encoder) {
super._toBinary(encoder) super._toBinary(encoder)
let len = this._content.length const len = this._length
encoding.writeVarUint(encoder, len) encoding.writeVarUint(encoder, len)
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let encoded let encoded
let content = this._content[i] const content = this._content[i]
if (content === undefined) { if (content === undefined) {
encoded = 'undefined' encoded = 'undefined'
} else { } else {
@ -66,7 +67,7 @@ export class ItemJSON extends Item {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('ItemJSON', this, `content:${JSON.stringify(this._content)}`) return stringify.logItemHelper('ItemJSON', this, `content:${JSON.stringify(this._content)}`)
} }
_splitAt (y, diff) { _splitAt (y, diff) {
if (diff === 0) { if (diff === 0) {

View File

@ -3,7 +3,7 @@
*/ */
import { Item, splitHelper } from './Item.js' import { Item, splitHelper } from './Item.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
@ -44,7 +44,7 @@ export class ItemString extends Item {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('ItemString', this, `content:"${this._content}"`) return stringify.logItemHelper('ItemString', this, `content:"${this._content}"`)
} }
_splitAt (y, diff) { _splitAt (y, diff) {
if (diff === 0) { if (diff === 0) {

View File

@ -6,7 +6,7 @@ import { defragmentItemContent } from '../utils/defragmentItemContent.js'
import Quill from 'quill' import Quill from 'quill'
import { GC } from '../structs/GC.js' import { GC } from '../structs/GC.js'
import * as random from '../lib/prng/prng.js' import * as random from '../lib/prng/prng.js'
import * as syncProtocol from '../protocols/syncProtocol.js' import * as syncProtocol from '../protocols/sync.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.js'
import { createMutex } from '../lib/mutex.js' import { createMutex } from '../lib/mutex.js'

58
tsconfig.json Normal file
View File

@ -0,0 +1,58 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2018",
"lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
"checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./build", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "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'. */
// "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. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}

View File

@ -5,7 +5,7 @@
import { Type } from '../structs/Type.js' import { Type } from '../structs/Type.js'
import { ItemJSON } from '../structs/ItemJSON.js' import { ItemJSON } from '../structs/ItemJSON.js'
import { ItemString } from '../structs/ItemString.js' import { ItemString } from '../structs/ItemString.js'
import { stringifyItemID, logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import { YEvent } from '../utils/YEvent.js' import { YEvent } from '../utils/YEvent.js'
import { Transaction } from '../utils/Transaction.js' // eslint-disable-line import { Transaction } from '../utils/Transaction.js' // eslint-disable-line
import { Item } from '../structs/Item.js' // eslint-disable-line import { Item } from '../structs/Item.js' // eslint-disable-line
@ -368,6 +368,6 @@ export class YArray extends Type {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('YArray', this, `start:${stringifyItemID(this._start)}"`) return stringify.logItemHelper('YArray', this, `start:${stringify.stringifyItemID(this._start)}"`)
} }
} }

View File

@ -5,7 +5,7 @@
import { Item } from '../structs/Item.js' import { Item } from '../structs/Item.js'
import { Type } from '../structs/Type.js' import { Type } from '../structs/Type.js'
import { ItemJSON } from '../structs/ItemJSON.js' import { ItemJSON } from '../structs/ItemJSON.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import { YEvent } from '../utils/YEvent.js' import { YEvent } from '../utils/YEvent.js'
/** /**
@ -175,6 +175,6 @@ export class YMap extends Type {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('YMap', this, `mapSize:${this._map.size}`) return stringify.logItemHelper('YMap', this, `mapSize:${this._map.size}`)
} }
} }

View File

@ -5,7 +5,7 @@
import { ItemEmbed } from '../structs/ItemEmbed.js' import { ItemEmbed } from '../structs/ItemEmbed.js'
import { ItemString } from '../structs/ItemString.js' import { ItemString } from '../structs/ItemString.js'
import { ItemFormat } from '../structs/ItemFormat.js' import { ItemFormat } from '../structs/ItemFormat.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
import { YArrayEvent, YArray } from './YArray.js' import { YArrayEvent, YArray } from './YArray.js'
/** /**
@ -702,6 +702,6 @@ export class YText extends YArray {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('YText', this) return stringify.logItemHelper('YText', this)
} }
} }

View File

@ -11,7 +11,7 @@ import { DomBinding } from '../bindings/dom/DomBinding.js' // eslint-disable-lin
import { YXmlTreeWalker } from './YXmlTreeWalker.js' import { YXmlTreeWalker } from './YXmlTreeWalker.js'
import { YArray } from './YArray.js' import { YArray } from './YArray.js'
import { YXmlEvent } from './YXmlEvent.js' import { YXmlEvent } from './YXmlEvent.js'
import { logItemHelper } from '../protocols/syncProtocol.js' import * as stringify from '../utils/structStringify.js'
/** /**
* Dom filter function. * Dom filter function.
@ -164,7 +164,7 @@ export class YXmlFragment extends YArray {
* @private * @private
*/ */
_logString () { _logString () {
return logItemHelper('YXml', this) return stringify.logItemHelper('YXml', this)
} }
} }

View File

@ -5,8 +5,8 @@
import { Tree } from '../lib/Tree.js' import { Tree } from '../lib/Tree.js'
import * as ID from '../utils/ID.js' import * as ID from '../utils/ID.js'
import { getStruct } from '../utils/structReferences.js' import { getStruct } from '../utils/structReferences.js'
import { stringifyID, stringifyItemID } from '../protocols/syncProtocol.js'
import { GC } from '../structs/GC.js' import { GC } from '../structs/GC.js'
import * as stringify from '../utils/structStringify.js'
export class OperationStore extends Tree { export class OperationStore extends Tree {
constructor (y) { constructor (y) {
@ -18,18 +18,18 @@ export class OperationStore extends Tree {
this.iterate(null, null, item => { this.iterate(null, null, item => {
if (item.constructor === GC) { if (item.constructor === GC) {
items.push({ items.push({
id: stringifyItemID(item), id: stringify.stringifyItemID(item),
content: item._length, content: item._length,
deleted: 'GC' deleted: 'GC'
}) })
} else { } else {
items.push({ items.push({
id: stringifyItemID(item), id: stringify.stringifyItemID(item),
origin: item._origin === null ? '()' : stringifyID(item._origin._lastId), origin: item._origin === null ? '()' : stringify.stringifyID(item._origin._lastId),
left: item._left === null ? '()' : stringifyID(item._left._lastId), left: item._left === null ? '()' : stringify.stringifyID(item._left._lastId),
right: stringifyItemID(item._right), right: stringify.stringifyItemID(item._right),
right_origin: stringifyItemID(item._right_origin), right_origin: stringify.stringifyItemID(item._right_origin),
parent: stringifyItemID(item._parent), parent: stringify.stringifyItemID(item._parent),
parentSub: item._parentSub, parentSub: item._parentSub,
deleted: item._deleted, deleted: item._deleted,
content: JSON.stringify(item._content) content: JSON.stringify(item._content)

View File

@ -4,8 +4,8 @@
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import { Y } from '../utils/Y.js' // eslint-disable-line import { Y } from '../utils/Y.js' // eslint-disable-line
import { Type } from '../structs/Type.js' // eslint-disable-line
import { Item } from '../structs/Item.js' // eslint-disable-line import { Item } from '../structs/Item.js' // eslint-disable-line
import { Type } from '../structs/Type.js' // eslint-disable-line
import { YEvent } from './YEvent.js' // eslint-disable-line import { YEvent } from './YEvent.js' // eslint-disable-line
/** /**
* A transaction is created for every change on the Yjs model. It is possible * A transaction is created for every change on the Yjs model. It is possible
@ -70,24 +70,3 @@ export class Transaction {
this.encodedStructs = encoding.createEncoder() this.encodedStructs = encoding.createEncoder()
} }
} }
export const writeStructToTransaction = (transaction, struct) => {
transaction.encodedStructsLen++
struct._toBinary(transaction.encodedStructs)
}
/**
* @private
*/
export const transactionTypeChanged = (y, type, sub) => {
if (type !== y && !type._deleted && !y._transaction.newTypes.has(type)) {
const changedTypes = y._transaction.changedTypes
let subs = changedTypes.get(type)
if (subs === undefined) {
// create if it doesn't exist yet
subs = new Set()
changedTypes.set(type, subs)
}
subs.add(sub)
}
}

View File

@ -6,7 +6,7 @@ import { createRootID } from './ID.js'
import { NamedEventHandler } from '../lib/NamedEventHandler.js' import { NamedEventHandler } from '../lib/NamedEventHandler.js'
import { Transaction } from './Transaction.js' import { Transaction } from './Transaction.js'
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.js'
import * as message from '../protocols/syncProtocol.js' import * as message from '../protocols/sync.js'
import { integrateRemoteStructs } from './integrateRemoteStructs.js' import { integrateRemoteStructs } from './integrateRemoteStructs.js'
import { Type } from '../structs/Type.js' // eslint-disable-line import { Type } from '../structs/Type.js' // eslint-disable-line
import { Decoder } from '../lib/decoding.js' // eslint-disable-line import { Decoder } from '../lib/decoding.js' // eslint-disable-line

5
utils/structEncoding.js Normal file
View File

@ -0,0 +1,5 @@
export const writeStructToTransaction = (transaction, struct) => {
transaction.encodedStructsLen++
struct._toBinary(transaction.encodedStructs)
}

47
utils/structStringify.js Normal file
View File

@ -0,0 +1,47 @@
import * as ID from './ID.js'
/**
* Stringify an item id.
*
* @param {ID.ID | ID.RootID} id
* @return {string}
*/
export const stringifyID = id => id instanceof ID.ID ? `(${id.user},${id.clock})` : `(${id.name},${id.type})`
/**
* Stringify an item as ID. HHere, an item could also be a Yjs instance (e.g. item._parent).
*
* @param {Item | Y | null} item
* @return {string}
*/
export const stringifyItemID = item => {
let result
if (item === null) {
result = '()'
} else if (item._id != null) {
result = stringifyID(item._id)
} else {
// must be a Yjs instance
// Don't include Y in this module, so we prevent circular dependencies.
result = 'y'
}
return result
}
/**
* Helper utility to convert an item to a readable format.
*
* @param {String} name The name of the item class (YText, ItemString, ..).
* @param {Item} item The item instance.
* @param {String} [append] Additional information to append to the returned
* string.
* @return {String} A readable string that represents the item object.
*
*/
export const logItemHelper = (name, item, append) => {
const left = item._left !== null ? stringifyID(item._left._lastId) : '()'
const origin = item._origin !== null ? stringifyID(item._origin._lastId) : '()'
return `${name}(id:${stringifyItemID(item)},left:${left},origin:${origin},right:${stringifyItemID(item._right)},parent:${stringifyItemID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
}