reimplemented WeakLink as an AbstractType
This commit is contained in:
		
							parent
							
								
									7b0d5fede5
								
							
						
					
					
						commit
						2c60c4a130
					
				@ -40,6 +40,5 @@ export * from './structs/ContentJSON.js'
 | 
			
		||||
export * from './structs/ContentAny.js'
 | 
			
		||||
export * from './structs/ContentString.js'
 | 
			
		||||
export * from './structs/ContentType.js'
 | 
			
		||||
export * from './structs/ContentLink.js'
 | 
			
		||||
export * from './structs/Item.js'
 | 
			
		||||
export * from './structs/Skip.js'
 | 
			
		||||
 | 
			
		||||
@ -1,183 +0,0 @@
 | 
			
		||||
import { decoding, encoding, error } from 'lib0'
 | 
			
		||||
import {
 | 
			
		||||
    UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore, // eslint-disable-line
 | 
			
		||||
    YWeakLink,
 | 
			
		||||
    AbstractType,
 | 
			
		||||
    getItemCleanStart,
 | 
			
		||||
    createID,
 | 
			
		||||
    getItemCleanEnd
 | 
			
		||||
  } from '../internals.js'
 | 
			
		||||
  
 | 
			
		||||
  export class ContentLink {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {YWeakLink<any>} link
 | 
			
		||||
     */
 | 
			
		||||
    constructor (link) {
 | 
			
		||||
      this.link = link
 | 
			
		||||
      /** 
 | 
			
		||||
       * @type {Item|null} 
 | 
			
		||||
       */
 | 
			
		||||
      this._item = null
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {number}
 | 
			
		||||
     */
 | 
			
		||||
    getLength () {
 | 
			
		||||
      return 1
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {Array<any>}
 | 
			
		||||
     */
 | 
			
		||||
    getContent () {
 | 
			
		||||
      return [this.link]
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    isCountable () {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {ContentLink}
 | 
			
		||||
     */
 | 
			
		||||
    copy () {
 | 
			
		||||
      return new ContentLink(this.link)
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {number} offset
 | 
			
		||||
     * @return {ContentLink}
 | 
			
		||||
     */
 | 
			
		||||
    splice (offset) {
 | 
			
		||||
      throw error.methodUnimplemented()
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {ContentLink} right
 | 
			
		||||
     * @return {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    mergeWith (right) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Transaction} transaction
 | 
			
		||||
     * @param {Item} item
 | 
			
		||||
     */
 | 
			
		||||
    integrate (transaction, item) {
 | 
			
		||||
      let sourceItem = this.link.item !== null ? this.link.item : getItemCleanStart(transaction, this.link.id)
 | 
			
		||||
      if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
 | 
			
		||||
        // for maps, advance to most recent item
 | 
			
		||||
        while (sourceItem.right !== null) {
 | 
			
		||||
          sourceItem = sourceItem.right
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!sourceItem.deleted && sourceItem.length > 1) {
 | 
			
		||||
        sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
 | 
			
		||||
      }
 | 
			
		||||
      this.link.item = sourceItem
 | 
			
		||||
      this._item = item
 | 
			
		||||
      if (!sourceItem.deleted) {
 | 
			
		||||
        const src = /** @type {Item} */ (sourceItem)
 | 
			
		||||
        if (src.linkedBy === null) {
 | 
			
		||||
          src.linkedBy = new Set()
 | 
			
		||||
        }
 | 
			
		||||
        src.linkedBy.add(item)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Transaction} transaction
 | 
			
		||||
     */
 | 
			
		||||
    delete (transaction) {
 | 
			
		||||
      if (this._item !== null && this.link !== null && this.link.item !== null && !this.link.item.deleted) {
 | 
			
		||||
        const item = /** @type {Item} */ (this.link.item)
 | 
			
		||||
        if (item.linkedBy !== null) {
 | 
			
		||||
          item.linkedBy.delete(this._item)
 | 
			
		||||
        }
 | 
			
		||||
        this.link.item = null
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {StructStore} store
 | 
			
		||||
     */
 | 
			
		||||
    gc (store) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 | 
			
		||||
     * @param {number} offset
 | 
			
		||||
     */
 | 
			
		||||
    write (encoder, offset) {
 | 
			
		||||
      const flags = 0 // flags that could be used in the future
 | 
			
		||||
      encoding.writeUint8(encoder.restEncoder, flags)
 | 
			
		||||
      encoder.writeLeftID(this.link.id)
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {number}
 | 
			
		||||
     */
 | 
			
		||||
    getRef () {
 | 
			
		||||
      return 11
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
 | 
			
		||||
   * @return {ContentLink}
 | 
			
		||||
   */
 | 
			
		||||
  export const readContentWeakLink = decoder => {
 | 
			
		||||
    const flags = decoding.readUint8(decoder.restDecoder)
 | 
			
		||||
    const id = decoder.readLeftID()
 | 
			
		||||
    return new ContentLink(new YWeakLink(id, null))
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
const lengthExceeded = error.create('Length exceeded!')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a {WeakLink} to an YArray element at given index.
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {Transaction} transaction
 | 
			
		||||
 * @param {AbstractType<any>} parent
 | 
			
		||||
 * @param {number} index
 | 
			
		||||
 * @return {YWeakLink<any>}
 | 
			
		||||
 */
 | 
			
		||||
export const arrayWeakLink = (transaction, parent, index) => {
 | 
			
		||||
  let item = parent._start
 | 
			
		||||
  for (; item !== null; item = item.right) {
 | 
			
		||||
    if (!item.deleted && item.countable) {
 | 
			
		||||
      if (index < item.length) {
 | 
			
		||||
        if (index > 0) {
 | 
			
		||||
            item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index))
 | 
			
		||||
        }
 | 
			
		||||
        if (item.length > 1) {
 | 
			
		||||
            item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock + 1))
 | 
			
		||||
        }
 | 
			
		||||
        return new YWeakLink(item.id, item)
 | 
			
		||||
      }
 | 
			
		||||
      index -= item.length
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  throw lengthExceeded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a {WeakLink} to an YMap element at given key.
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {AbstractType<any>} parent
 | 
			
		||||
 * @param {string} key
 | 
			
		||||
 * @return {YWeakLink<any>|undefined}
 | 
			
		||||
 */
 | 
			
		||||
export const mapWeakLink = (parent, key) => {
 | 
			
		||||
  const item = parent._map.get(key)
 | 
			
		||||
  if (item !== undefined) {
 | 
			
		||||
    return new YWeakLink(item.id, item)
 | 
			
		||||
  } else {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -7,7 +7,8 @@ import {
 | 
			
		||||
  readYXmlFragment,
 | 
			
		||||
  readYXmlHook,
 | 
			
		||||
  readYXmlText,
 | 
			
		||||
  UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line
 | 
			
		||||
  UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, Item, YEvent, AbstractType, // eslint-disable-line
 | 
			
		||||
  readYWeakLink
 | 
			
		||||
} from '../internals.js'
 | 
			
		||||
 | 
			
		||||
import * as error from 'lib0/error'
 | 
			
		||||
@ -23,7 +24,8 @@ export const typeRefs = [
 | 
			
		||||
  readYXmlElement,
 | 
			
		||||
  readYXmlFragment,
 | 
			
		||||
  readYXmlHook,
 | 
			
		||||
  readYXmlText
 | 
			
		||||
  readYXmlText,
 | 
			
		||||
  readYWeakLink
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const YArrayRefID = 0
 | 
			
		||||
@ -33,6 +35,7 @@ export const YXmlElementRefID = 3
 | 
			
		||||
export const YXmlFragmentRefID = 4
 | 
			
		||||
export const YXmlHookRefID = 5
 | 
			
		||||
export const YXmlTextRefID = 6
 | 
			
		||||
export const YWeakLinkRefID = 7
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @private
 | 
			
		||||
@ -104,6 +107,7 @@ export class ContentType {
 | 
			
		||||
   * @param {Transaction} transaction
 | 
			
		||||
   */
 | 
			
		||||
  delete (transaction) {
 | 
			
		||||
    this.type._delete(transaction) // call custom destructor on AbstractType
 | 
			
		||||
    let item = this.type._start
 | 
			
		||||
    while (item !== null) {
 | 
			
		||||
      if (!item.deleted) {
 | 
			
		||||
 | 
			
		||||
@ -18,15 +18,13 @@ import {
 | 
			
		||||
  readContentString,
 | 
			
		||||
  readContentEmbed,
 | 
			
		||||
  readContentDoc,
 | 
			
		||||
  readContentWeakLink,
 | 
			
		||||
  createID,
 | 
			
		||||
  readContentFormat,
 | 
			
		||||
  readContentType,
 | 
			
		||||
  addChangedTypeToTransaction,
 | 
			
		||||
  isDeleted,
 | 
			
		||||
  StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
 | 
			
		||||
  YWeakLink,
 | 
			
		||||
  ContentLink
 | 
			
		||||
  YWeakLink
 | 
			
		||||
} from '../internals.js'
 | 
			
		||||
 | 
			
		||||
import * as error from 'lib0/error'
 | 
			
		||||
@ -302,7 +300,7 @@ export class Item extends AbstractStruct {
 | 
			
		||||
     * If this item was referenced by other weak links, here we keep the references
 | 
			
		||||
     * to these weak refs.
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {Set<Item> | null}
 | 
			
		||||
     * @type {Set<YWeakLink<any>> | null}
 | 
			
		||||
     */
 | 
			
		||||
    this.linkedBy = null
 | 
			
		||||
    /**
 | 
			
		||||
@ -386,10 +384,10 @@ export class Item extends AbstractStruct {
 | 
			
		||||
    if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) {
 | 
			
		||||
      return this.parent.client
 | 
			
		||||
    }
 | 
			
		||||
    if (this.content.constructor === ContentLink) {
 | 
			
		||||
      const content = /** @type {ContentLink} */ (this.content)
 | 
			
		||||
      if (content.link.id.client !== this.id.client) {
 | 
			
		||||
        return content.link.id.client
 | 
			
		||||
    if (this.content.constructor === ContentType && /** @type {ContentType} */ (this.content).type.constructor === YWeakLink) {
 | 
			
		||||
      const content = /** @type {any} */ (this.content).type
 | 
			
		||||
      if (content._id.client !== this.id.client) {
 | 
			
		||||
        return content._id.client
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -540,7 +538,7 @@ export class Item extends AbstractStruct {
 | 
			
		||||
      addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
 | 
			
		||||
      if (this.linkedBy !== null) {
 | 
			
		||||
        for (let link of this.linkedBy) {
 | 
			
		||||
          addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (link.parent), link.parentSub)
 | 
			
		||||
          addChangedTypeToTransaction(transaction, link, this.parentSub)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.parentSub !== null && this.right !== null)) {
 | 
			
		||||
@ -647,9 +645,14 @@ export class Item extends AbstractStruct {
 | 
			
		||||
      addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
 | 
			
		||||
      addChangedTypeToTransaction(transaction, parent, this.parentSub)
 | 
			
		||||
      this.content.delete(transaction)
 | 
			
		||||
      if (this.linkedBy !== null) {
 | 
			
		||||
        for (let link of this.linkedBy) {
 | 
			
		||||
          addChangedTypeToTransaction(transaction, link, this.parentSub)
 | 
			
		||||
        }
 | 
			
		||||
        this.linkedBy = null
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {StructStore} store
 | 
			
		||||
@ -744,8 +747,7 @@ export const contentRefs = [
 | 
			
		||||
  readContentType, // 7
 | 
			
		||||
  readContentAny, // 8
 | 
			
		||||
  readContentDoc, // 9
 | 
			
		||||
  () => { error.unexpectedCase() }, // 10 - Skip is not ItemContent
 | 
			
		||||
  readContentWeakLink // 11
 | 
			
		||||
  () => { error.unexpectedCase() } // 10 - Skip is not ItemContent
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import {
 | 
			
		||||
  ContentAny,
 | 
			
		||||
  ContentBinary,
 | 
			
		||||
  getItemCleanStart,
 | 
			
		||||
  ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, YWeakLink, ContentLink, // eslint-disable-line
 | 
			
		||||
  ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, YWeakLink, // eslint-disable-line
 | 
			
		||||
} from '../internals.js'
 | 
			
		||||
 | 
			
		||||
import * as map from 'lib0/map'
 | 
			
		||||
@ -309,6 +309,11 @@ export class AbstractType {
 | 
			
		||||
    this._item = item
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {Transaction} transaction 
 | 
			
		||||
   */
 | 
			
		||||
  _delete (transaction) { }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return {AbstractType<EventType>}
 | 
			
		||||
   */
 | 
			
		||||
@ -669,10 +674,6 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
 | 
			
		||||
              left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
 | 
			
		||||
              left.integrate(transaction, 0)
 | 
			
		||||
              break
 | 
			
		||||
            case YWeakLink:
 | 
			
		||||
              left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentLink(/** @type {YWeakLink<any>} */ (c)))
 | 
			
		||||
              left.integrate(transaction, 0)
 | 
			
		||||
              break
 | 
			
		||||
            default:
 | 
			
		||||
              if (c instanceof AbstractType) {
 | 
			
		||||
                left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
 | 
			
		||||
@ -855,9 +856,6 @@ export const typeMapSet = (transaction, parent, key, value) => {
 | 
			
		||||
      case Doc:
 | 
			
		||||
        content = new ContentDoc(/** @type {Doc} */ (value))
 | 
			
		||||
        break
 | 
			
		||||
      case YWeakLink:
 | 
			
		||||
        content = new ContentLink(/** @type {YWeakLink<any>} */ (value))
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        if (value instanceof AbstractType) {
 | 
			
		||||
          content = new ContentType(value)
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,13 @@
 | 
			
		||||
import { AbstractType, GC, ID, Item, Transaction, YEvent } from "yjs"
 | 
			
		||||
import { decoding, encoding, error } from "lib0"
 | 
			
		||||
import { 
 | 
			
		||||
  YEvent, Transaction, ID, GC, AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Item,
 | 
			
		||||
  transact,
 | 
			
		||||
  getItemCleanEnd,
 | 
			
		||||
  createID,
 | 
			
		||||
  getItemCleanStart,
 | 
			
		||||
  callTypeObservers,
 | 
			
		||||
  YWeakLinkRefID
 | 
			
		||||
} from "../internals.js"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @template T extends AbstractType<any>
 | 
			
		||||
@ -9,27 +18,27 @@ export class YWeakLinkEvent extends YEvent {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {YWeakLink<T>} ylink The YWeakLink to which this event was propagated to.
 | 
			
		||||
   * @param {Transaction} transaction
 | 
			
		||||
   * @param {YEvent<any>} source Source event that has been propagated to ylink.
 | 
			
		||||
   */
 | 
			
		||||
  constructor (ylink, transaction, source) {
 | 
			
		||||
  constructor (ylink, transaction) {
 | 
			
		||||
    super(ylink, transaction)
 | 
			
		||||
    this.source = source
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends AbstractType<YWeakLinkEvent<T>>
 | 
			
		||||
 * 
 | 
			
		||||
 * Weak link to another value stored somewhere in the document.
 | 
			
		||||
 */
 | 
			
		||||
export class YWeakLink {
 | 
			
		||||
export class YWeakLink extends AbstractType {
 | 
			
		||||
  /**
 | 
			
		||||
    * @param {ID} id
 | 
			
		||||
    * @param {Item|GC|null} item
 | 
			
		||||
    */
 | 
			
		||||
  constructor(id, item) {
 | 
			
		||||
    this.id = id
 | 
			
		||||
    this.item = item
 | 
			
		||||
    super()
 | 
			
		||||
    this._id = id
 | 
			
		||||
    this._linkedItem = item
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
@ -38,14 +47,14 @@ export class YWeakLink {
 | 
			
		||||
   * @return {T|undefined}
 | 
			
		||||
   */
 | 
			
		||||
  deref() {
 | 
			
		||||
    if (this.item !== null && this.item.constructor === Item) {
 | 
			
		||||
      let item = this.item
 | 
			
		||||
    if (this._linkedItem !== null && this._linkedItem.constructor === Item) {
 | 
			
		||||
      let item = this._linkedItem
 | 
			
		||||
      if (item.parentSub !== null) {
 | 
			
		||||
        // for map types go to the most recent one
 | 
			
		||||
        while (item.right !== null) {
 | 
			
		||||
          item = item.right
 | 
			
		||||
        }
 | 
			
		||||
        this.item = item
 | 
			
		||||
        this._linkedItem = item
 | 
			
		||||
      }
 | 
			
		||||
      if (!item.deleted) {
 | 
			
		||||
        return item.content.getContent()[0]
 | 
			
		||||
@ -53,4 +62,155 @@ export class YWeakLink {
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Integrate this type into the Yjs instance.
 | 
			
		||||
   *
 | 
			
		||||
   * * Save this struct in the os
 | 
			
		||||
   * * This type is sent to other client
 | 
			
		||||
   * * Observer functions are fired
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Doc} y The Yjs instance
 | 
			
		||||
   * @param {Item|null} item
 | 
			
		||||
   */
 | 
			
		||||
  _integrate (y, item) {
 | 
			
		||||
    super._integrate(y, item)
 | 
			
		||||
    if (item !== null) {
 | 
			
		||||
      transact(y, (transaction) => {
 | 
			
		||||
        let sourceItem = this._linkedItem !== null ? this._linkedItem : getItemCleanStart(transaction, this._id)
 | 
			
		||||
        if (sourceItem.constructor === Item && sourceItem.parentSub !== null) {
 | 
			
		||||
          // for maps, advance to most recent item
 | 
			
		||||
          while (sourceItem.right !== null) {
 | 
			
		||||
            sourceItem = sourceItem.right
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (!sourceItem.deleted && sourceItem.length > 1) {
 | 
			
		||||
          sourceItem = getItemCleanEnd(transaction, transaction.doc.store, createID(sourceItem.id.client, sourceItem.id.clock + 1))
 | 
			
		||||
        }
 | 
			
		||||
        this._linkedItem = sourceItem
 | 
			
		||||
        if (!sourceItem.deleted) {
 | 
			
		||||
          const src = /** @type {Item} */ (sourceItem)
 | 
			
		||||
          if (src.linkedBy === null) {
 | 
			
		||||
            src.linkedBy = new Set()
 | 
			
		||||
          }
 | 
			
		||||
          src.linkedBy.add(this)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {Transaction} transaction
 | 
			
		||||
   */
 | 
			
		||||
  _delete (transaction) {
 | 
			
		||||
    if (this._item !== null && this._linkedItem !== null && !this._linkedItem.deleted) {
 | 
			
		||||
      const item = /** @type {Item} */ (this._linkedItem)
 | 
			
		||||
      if (item.linkedBy !== null) {
 | 
			
		||||
        item.linkedBy.delete(this)
 | 
			
		||||
      }
 | 
			
		||||
      this._linkedItem = null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return {YWeakLink<T>}
 | 
			
		||||
   */
 | 
			
		||||
  _copy () {
 | 
			
		||||
    return new YWeakLink(this._id, this._linkedItem)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return {YWeakLink<T>}
 | 
			
		||||
   */
 | 
			
		||||
  clone () {
 | 
			
		||||
    return new YWeakLink(this._id, this._linkedItem)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates YWeakLinkEvent and calls observers.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Transaction} transaction
 | 
			
		||||
   * @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
 | 
			
		||||
   */
 | 
			
		||||
  _callObserver (transaction, parentSubs) {
 | 
			
		||||
    super._callObserver(transaction, parentSubs)
 | 
			
		||||
    callTypeObservers(this, transaction, new YWeakLinkEvent(this, transaction))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
 | 
			
		||||
   */
 | 
			
		||||
  _write (encoder) {
 | 
			
		||||
    encoder.writeTypeRef(YWeakLinkRefID)
 | 
			
		||||
    const flags = 0 // flags that could be used in the future
 | 
			
		||||
    encoding.writeUint8(encoder.restEncoder, flags)
 | 
			
		||||
    encoder.writeLeftID(this._id)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
/**
 | 
			
		||||
 * @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
 | 
			
		||||
 * @return {YWeakLink<any>}
 | 
			
		||||
 */
 | 
			
		||||
export const readYWeakLink = decoder => {
 | 
			
		||||
  const flags = decoding.readUint8(decoder.restDecoder)
 | 
			
		||||
  const id = decoder.readLeftID()
 | 
			
		||||
  return new YWeakLink(id, null)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const lengthExceeded = error.create('Length exceeded!')
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a {WeakLink} to an YArray element at given index.
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {Transaction} transaction
 | 
			
		||||
 * @param {AbstractType<any>} parent
 | 
			
		||||
 * @param {number} index
 | 
			
		||||
 * @return {YWeakLink<any>}
 | 
			
		||||
 */
 | 
			
		||||
export const arrayWeakLink = (transaction, parent, index) => {
 | 
			
		||||
  let item = parent._start
 | 
			
		||||
  for (; item !== null; item = item.right) {
 | 
			
		||||
    if (!item.deleted && item.countable) {
 | 
			
		||||
      if (index < item.length) {
 | 
			
		||||
        if (index > 0) {
 | 
			
		||||
            item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index))
 | 
			
		||||
        }
 | 
			
		||||
        if (item.length > 1) {
 | 
			
		||||
            item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock))
 | 
			
		||||
        }
 | 
			
		||||
        const link = new YWeakLink(item.id, item)
 | 
			
		||||
        if (item.linkedBy === null) {
 | 
			
		||||
          item.linkedBy = new Set()
 | 
			
		||||
        }
 | 
			
		||||
        item.linkedBy.add(link)
 | 
			
		||||
        return link
 | 
			
		||||
      }
 | 
			
		||||
      index -= item.length
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  throw lengthExceeded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a {WeakLink} to an YMap element at given key.
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {AbstractType<any>} parent
 | 
			
		||||
 * @param {string} key
 | 
			
		||||
 * @return {YWeakLink<any>|undefined}
 | 
			
		||||
 */
 | 
			
		||||
export const mapWeakLink = (parent, key) => {
 | 
			
		||||
  const item = parent._map.get(key)
 | 
			
		||||
  if (item !== undefined) {
 | 
			
		||||
    const link = new YWeakLink(item.id, item)
 | 
			
		||||
    if (item.linkedBy === null) {
 | 
			
		||||
      item.linkedBy = new Set()
 | 
			
		||||
    }
 | 
			
		||||
    item.linkedBy.add(link)
 | 
			
		||||
    return link
 | 
			
		||||
  } else {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -118,55 +118,69 @@ export const testDeleteSource = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveMapLinkArrayRemove = tc => {
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map = doc.getMap('map')
 | 
			
		||||
  const array = doc.getArray('array')
 | 
			
		||||
 | 
			
		||||
  array.insert(0, [1])
 | 
			
		||||
  const link = array.link(0)
 | 
			
		||||
  map.set('key', link)
 | 
			
		||||
export const testObserveMapUpdate = tc => {
 | 
			
		||||
  const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
 | 
			
		||||
  map0.set('a', 'value')
 | 
			
		||||
  const link0 = /** @type {Y.WeakLink<String>} */ (map0.link('a'))
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {any}
 | 
			
		||||
   */
 | 
			
		||||
  let keys = null
 | 
			
		||||
  map.observe((e) => {
 | 
			
		||||
    console.log('map received event', e)
 | 
			
		||||
    keys = e.keys
 | 
			
		||||
  })
 | 
			
		||||
  let target0
 | 
			
		||||
  link0.observe((e) => target0 = e.target)
 | 
			
		||||
  map0.set('b', link0)
 | 
			
		||||
 | 
			
		||||
  array.delete(0)
 | 
			
		||||
  testConnector.flushAllMessages()
 | 
			
		||||
 | 
			
		||||
  t.compare(keys.get('key'), { action:'delete', oldValue: 1, newValue: null })
 | 
			
		||||
  let link1 = /** @type {Y.WeakLink<String>} */ (map1.get('b'))
 | 
			
		||||
  t.compare(link1.deref(), 'value')
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {any}
 | 
			
		||||
   */
 | 
			
		||||
  let target1
 | 
			
		||||
  link1.observe((e) => target1 = e.target)
 | 
			
		||||
 | 
			
		||||
  map0.set('a', 'value2')
 | 
			
		||||
  t.compare(target0.deref(), 'value2')
 | 
			
		||||
 | 
			
		||||
  testConnector.flushAllMessages()
 | 
			
		||||
  t.compare(target1.deref(), 'value2')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveMapLinkMapUpdate = tc => {
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map1 = doc.getMap('map1')
 | 
			
		||||
  const map2 = doc.getMap('map2')
 | 
			
		||||
export const testObserveMapDelete = tc => {
 | 
			
		||||
  const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
 | 
			
		||||
  map0.set('a', 'value')
 | 
			
		||||
  const link0 = /** @type {Y.WeakLink<String>} */ (map0.link('a'))
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
 | 
			
		||||
   * @type {any}
 | 
			
		||||
   */
 | 
			
		||||
  let keys
 | 
			
		||||
  map1.observe((e) => keys = e.keys)
 | 
			
		||||
  let target0
 | 
			
		||||
  link0.observe((e) => target0 = e.target)
 | 
			
		||||
  map0.set('b', link0)
 | 
			
		||||
 | 
			
		||||
  map2.set('key', 'value1')
 | 
			
		||||
  const link = map2.link('key')
 | 
			
		||||
  map1.set('other-key', link)
 | 
			
		||||
  testConnector.flushAllMessages()
 | 
			
		||||
 | 
			
		||||
  keys = /** @type {any} */ (null)
 | 
			
		||||
  map2.set('key', 'value2')
 | 
			
		||||
  let link1 = /** @type {Y.WeakLink<String>} */ (map1.get('b'))
 | 
			
		||||
  t.compare(link1.deref(), 'value')
 | 
			
		||||
  /**
 | 
			
		||||
   * @type {any}
 | 
			
		||||
   */
 | 
			
		||||
  let target1
 | 
			
		||||
  link1.observe((e) => target1 = e.target)
 | 
			
		||||
 | 
			
		||||
  t.compare(keys.get('key'), { action:'update', oldValue: 'value1', newValue: 'value2' })
 | 
			
		||||
  map0.delete('a')
 | 
			
		||||
  t.compare(target0.deref(), undefined)
 | 
			
		||||
 | 
			
		||||
  testConnector.flushAllMessages()
 | 
			
		||||
  t.compare(target1.deref(), undefined)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveMapLinkMapRemove = tc => {
 | 
			
		||||
 const testObserveMapLinkMapRemove = tc => {
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map1 = doc.getMap('map1')
 | 
			
		||||
  const map2 = doc.getMap('map2')
 | 
			
		||||
@ -189,7 +203,7 @@ export const testObserveMapLinkMapRemove = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveArrayLinkMapRemove = tc => {
 | 
			
		||||
 const testObserveArrayLinkMapRemove = tc => {
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const array = doc.getArray('array')
 | 
			
		||||
  const map = doc.getMap('map')
 | 
			
		||||
@ -212,7 +226,7 @@ export const testObserveArrayLinkMapRemove = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveArrayLinkMapUpdate = tc => {
 | 
			
		||||
 const testObserveArrayLinkMapUpdate = tc => {
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const array = doc.getArray('array')
 | 
			
		||||
  const map = doc.getMap('map')
 | 
			
		||||
@ -235,7 +249,7 @@ export const testObserveArrayLinkMapUpdate = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testObserveTransitive = tc => {
 | 
			
		||||
 const testObserveTransitive = tc => {
 | 
			
		||||
  // test observers in a face of linked chains of values
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map1 = doc.getMap('map1')
 | 
			
		||||
@ -262,7 +276,7 @@ export const testObserveTransitive = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testDeepObserveMap = tc => {
 | 
			
		||||
 const testDeepObserveMap = tc => {
 | 
			
		||||
  // test observers in a face of linked chains of values
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map = doc.getMap('map')
 | 
			
		||||
@ -297,7 +311,7 @@ export const testDeepObserveMap = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testDeepObserveArray = tc => {
 | 
			
		||||
 const testDeepObserveArray = tc => {
 | 
			
		||||
  // test observers in a face of linked chains of values
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const map = doc.getMap('map')
 | 
			
		||||
@ -332,7 +346,7 @@ export const testDeepObserveArray = tc => {
 | 
			
		||||
/**
 | 
			
		||||
 * @param {t.TestCase} tc
 | 
			
		||||
 */
 | 
			
		||||
export const testDeepObserveRecursive = tc => {
 | 
			
		||||
 const testDeepObserveRecursive = tc => {
 | 
			
		||||
  // test observers in a face of linked chains of values
 | 
			
		||||
  const doc = new Y.Doc()
 | 
			
		||||
  const root = doc.getArray('array')
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user