implement snapshot & event.changes
This commit is contained in:
		
							parent
							
								
									8bcff6138c
								
							
						
					
					
						commit
						bb1c0b809f
					
				@ -38,7 +38,8 @@ export {
 | 
				
			|||||||
  getState,
 | 
					  getState,
 | 
				
			||||||
  Snapshot,
 | 
					  Snapshot,
 | 
				
			||||||
  createSnapshot,
 | 
					  createSnapshot,
 | 
				
			||||||
  createSnapshotFromDoc,
 | 
					  snapshot,
 | 
				
			||||||
 | 
					  emptySnapshot,
 | 
				
			||||||
  findRootTypeKey,
 | 
					  findRootTypeKey,
 | 
				
			||||||
  typeListToArraySnapshot,
 | 
					  typeListToArraySnapshot,
 | 
				
			||||||
  typeMapGetSnapshot,
 | 
					  typeMapGetSnapshot,
 | 
				
			||||||
@ -46,5 +47,9 @@ export {
 | 
				
			|||||||
  applyUpdate,
 | 
					  applyUpdate,
 | 
				
			||||||
  encodeStateAsUpdate,
 | 
					  encodeStateAsUpdate,
 | 
				
			||||||
  encodeStateVector,
 | 
					  encodeStateVector,
 | 
				
			||||||
  UndoManager
 | 
					  UndoManager,
 | 
				
			||||||
 | 
					  decodeSnapshot,
 | 
				
			||||||
 | 
					  encodeSnapshot,
 | 
				
			||||||
 | 
					  isDeleted,
 | 
				
			||||||
 | 
					  equalSnapshots
 | 
				
			||||||
} from './internals.js'
 | 
					} from './internals.js'
 | 
				
			||||||
 | 
				
			|||||||
@ -584,7 +584,7 @@ export const typeMapHas = (parent, key) => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const typeMapGetSnapshot = (parent, key, snapshot) => {
 | 
					export const typeMapGetSnapshot = (parent, key, snapshot) => {
 | 
				
			||||||
  let v = parent._map.get(key) || null
 | 
					  let v = parent._map.get(key) || null
 | 
				
			||||||
  while (v !== null && (!snapshot.sm.has(v.id.client) || v.id.clock >= (snapshot.sm.get(v.id.client) || 0))) {
 | 
					  while (v !== null && (!snapshot.sv.has(v.id.client) || v.id.clock >= (snapshot.sv.get(v.id.client) || 0))) {
 | 
				
			||||||
    v = v.left
 | 
					    v = v.left
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
 | 
					  return v !== null && isVisible(v, snapshot) ? v.content.getContent()[v.length - 1] : undefined
 | 
				
			||||||
 | 
				
			|||||||
@ -169,6 +169,8 @@ export const addToDeleteSet = (ds, id, length) => {
 | 
				
			|||||||
  map.setIfUndefined(ds.clients, id.client, () => []).push(new DeleteItem(id.clock, length))
 | 
					  map.setIfUndefined(ds.clients, id.client, () => []).push(new DeleteItem(id.clock, length))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createDeleteSet = () => new DeleteSet()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {StructStore} ss
 | 
					 * @param {StructStore} ss
 | 
				
			||||||
 * @return {DeleteSet} Merged and sorted DeleteSet
 | 
					 * @return {DeleteSet} Merged and sorted DeleteSet
 | 
				
			||||||
@ -177,7 +179,7 @@ export const addToDeleteSet = (ds, id, length) => {
 | 
				
			|||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const createDeleteSetFromStructStore = ss => {
 | 
					export const createDeleteSetFromStructStore = ss => {
 | 
				
			||||||
  const ds = new DeleteSet()
 | 
					  const ds = createDeleteSet()
 | 
				
			||||||
  ss.clients.forEach((structs, client) => {
 | 
					  ss.clients.forEach((structs, client) => {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @type {Array<DeleteItem>}
 | 
					     * @type {Array<DeleteItem>}
 | 
				
			||||||
@ -224,6 +226,26 @@ export const writeDeleteSet = (encoder, ds) => {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {decoding.Decoder} decoder
 | 
				
			||||||
 | 
					 * @return {DeleteSet}
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @private
 | 
				
			||||||
 | 
					 * @function
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const readDeleteSet = decoder => {
 | 
				
			||||||
 | 
					  const ds = new DeleteSet()
 | 
				
			||||||
 | 
					  const numClients = decoding.readVarUint(decoder)
 | 
				
			||||||
 | 
					  for (let i = 0; i < numClients; i++) {
 | 
				
			||||||
 | 
					    const client = decoding.readVarUint(decoder)
 | 
				
			||||||
 | 
					    const numberOfDeletes = decoding.readVarUint(decoder)
 | 
				
			||||||
 | 
					    for (let i = 0; i < numberOfDeletes; i++) {
 | 
				
			||||||
 | 
					      addToDeleteSet(ds, createID(client, decoding.readVarUint(decoder)), decoding.readVarUint(decoder))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return ds
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {decoding.Decoder} decoder
 | 
					 * @param {decoding.Decoder} decoder
 | 
				
			||||||
 * @param {Transaction} transaction
 | 
					 * @param {Transaction} transaction
 | 
				
			||||||
@ -232,7 +254,7 @@ export const writeDeleteSet = (encoder, ds) => {
 | 
				
			|||||||
 * @private
 | 
					 * @private
 | 
				
			||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const readDeleteSet = (decoder, transaction, store) => {
 | 
					export const readAndApplyDeleteSet = (decoder, transaction, store) => {
 | 
				
			||||||
  const unappliedDS = new DeleteSet()
 | 
					  const unappliedDS = new DeleteSet()
 | 
				
			||||||
  const numClients = decoding.readVarUint(decoder)
 | 
					  const numClients = decoding.readVarUint(decoder)
 | 
				
			||||||
  for (let i = 0; i < numClients; i++) {
 | 
					  for (let i = 0; i < numClients; i++) {
 | 
				
			||||||
@ -279,6 +301,7 @@ export const readDeleteSet = (decoder, transaction, store) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (unappliedDS.clients.size > 0) {
 | 
					  if (unappliedDS.clients.size > 0) {
 | 
				
			||||||
 | 
					    // TODO: no need for encoding+decoding ds anymore
 | 
				
			||||||
    const unappliedDSEncoder = encoding.createEncoder()
 | 
					    const unappliedDSEncoder = encoding.createEncoder()
 | 
				
			||||||
    writeDeleteSet(unappliedDSEncoder, unappliedDS)
 | 
					    writeDeleteSet(unappliedDSEncoder, unappliedDS)
 | 
				
			||||||
    store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
 | 
					    store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder)))
 | 
				
			||||||
 | 
				
			|||||||
@ -6,18 +6,26 @@ import {
 | 
				
			|||||||
  getItemCleanStart,
 | 
					  getItemCleanStart,
 | 
				
			||||||
  createID,
 | 
					  createID,
 | 
				
			||||||
  iterateDeletedStructs,
 | 
					  iterateDeletedStructs,
 | 
				
			||||||
 | 
					  writeDeleteSet,
 | 
				
			||||||
 | 
					  writeStateVector,
 | 
				
			||||||
 | 
					  readDeleteSet,
 | 
				
			||||||
 | 
					  readStateVector,
 | 
				
			||||||
 | 
					  createDeleteSet,
 | 
				
			||||||
 | 
					  getState,
 | 
				
			||||||
  Transaction, Doc, DeleteSet, Item // eslint-disable-line
 | 
					  Transaction, Doc, DeleteSet, Item // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as map from 'lib0/map.js'
 | 
					import * as map from 'lib0/map.js'
 | 
				
			||||||
import * as set from 'lib0/set.js'
 | 
					import * as set from 'lib0/set.js'
 | 
				
			||||||
 | 
					import * as encoding from 'lib0/encoding.js'
 | 
				
			||||||
 | 
					import * as decoding from 'lib0/decoding.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Snapshot {
 | 
					export class Snapshot {
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @param {DeleteSet} ds
 | 
					   * @param {DeleteSet} ds
 | 
				
			||||||
   * @param {Map<number,number>} sm state map
 | 
					   * @param {Map<number,number>} sv state map
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  constructor (ds, sm) {
 | 
					  constructor (ds, sv) {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @type {DeleteSet}
 | 
					     * @type {DeleteSet}
 | 
				
			||||||
     * @private
 | 
					     * @private
 | 
				
			||||||
@ -28,10 +36,64 @@ export class Snapshot {
 | 
				
			|||||||
     * @type {Map<number,number>}
 | 
					     * @type {Map<number,number>}
 | 
				
			||||||
     * @private
 | 
					     * @private
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.sm = sm
 | 
					    this.sv = sv
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {Snapshot} snap1
 | 
				
			||||||
 | 
					 * @param {Snapshot} snap2
 | 
				
			||||||
 | 
					 * @return {boolean}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const equalSnapshots = (snap1, snap2) => {
 | 
				
			||||||
 | 
					  const ds1 = snap1.ds.clients
 | 
				
			||||||
 | 
					  const ds2 = snap2.ds.clients
 | 
				
			||||||
 | 
					  const sv1 = snap1.sv
 | 
				
			||||||
 | 
					  const sv2 = snap2.sv
 | 
				
			||||||
 | 
					  if (sv1.size !== sv2.size || ds1.size !== ds2.size) {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (const [key, value] of sv1) {
 | 
				
			||||||
 | 
					    if (sv2.get(key) !== value) {
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (const [client, dsitems1] of ds1) {
 | 
				
			||||||
 | 
					    const dsitems2 = ds2.get(client) || []
 | 
				
			||||||
 | 
					    if (dsitems1.length !== dsitems2.length) {
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (let i = 0; i < dsitems1.length; i++) {
 | 
				
			||||||
 | 
					      const dsitem1 = dsitems1[i]
 | 
				
			||||||
 | 
					      const dsitem2 = dsitems2[i]
 | 
				
			||||||
 | 
					      if (dsitem1.clock !== dsitem2.clock || dsitem1.len !== dsitem2.len) {
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {Snapshot} snapshot
 | 
				
			||||||
 | 
					 * @return {Uint8Array}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const encodeSnapshot = snapshot => {
 | 
				
			||||||
 | 
					  const encoder = encoding.createEncoder()
 | 
				
			||||||
 | 
					  writeDeleteSet(encoder, snapshot.ds)
 | 
				
			||||||
 | 
					  writeStateVector(encoder, snapshot.sv)
 | 
				
			||||||
 | 
					  return encoding.toUint8Array(encoder)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {Uint8Array} buf
 | 
				
			||||||
 | 
					 * @return {Snapshot}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const decodeSnapshot = buf => {
 | 
				
			||||||
 | 
					  const decoder = decoding.createDecoder(buf)
 | 
				
			||||||
 | 
					  return new Snapshot(readDeleteSet(decoder), readStateVector(decoder))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {DeleteSet} ds
 | 
					 * @param {DeleteSet} ds
 | 
				
			||||||
 * @param {Map<number,number>} sm
 | 
					 * @param {Map<number,number>} sm
 | 
				
			||||||
@ -39,11 +101,13 @@ export class Snapshot {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
 | 
					export const createSnapshot = (ds, sm) => new Snapshot(ds, sm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const emptySnapshot = createSnapshot(createDeleteSet(), new Map())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {Doc} doc
 | 
					 * @param {Doc} doc
 | 
				
			||||||
 * @return {Snapshot}
 | 
					 * @return {Snapshot}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
 | 
					export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc.store), getStateVector(doc.store))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {Item} item
 | 
					 * @param {Item} item
 | 
				
			||||||
@ -53,7 +117,7 @@ export const createSnapshotFromDoc = doc => createSnapshot(createDeleteSetFromSt
 | 
				
			|||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
 | 
					export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
 | 
				
			||||||
  snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
 | 
					  snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -65,8 +129,10 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
 | 
				
			|||||||
  const store = transaction.doc.store
 | 
					  const store = transaction.doc.store
 | 
				
			||||||
  // check if we already split for this snapshot
 | 
					  // check if we already split for this snapshot
 | 
				
			||||||
  if (!meta.has(snapshot)) {
 | 
					  if (!meta.has(snapshot)) {
 | 
				
			||||||
    snapshot.sm.forEach((clock, client) => {
 | 
					    snapshot.sv.forEach((clock, client) => {
 | 
				
			||||||
      getItemCleanStart(transaction, store, createID(client, clock))
 | 
					      if (clock < getState(store, client)) {
 | 
				
			||||||
 | 
					        getItemCleanStart(transaction, store, createID(client, clock))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
 | 
					    iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
 | 
				
			||||||
    meta.add(snapshot)
 | 
					    meta.add(snapshot)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  isDeleted,
 | 
					  isDeleted,
 | 
				
			||||||
  AbstractType, Transaction, AbstractStruct // eslint-disable-line
 | 
					  Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as set from 'lib0/set.js'
 | 
				
			||||||
 | 
					import * as array from 'lib0/array.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * YEvent describes the changes on a YType.
 | 
					 * YEvent describes the changes on a YType.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -28,6 +31,10 @@ export class YEvent {
 | 
				
			|||||||
     * @type {Transaction}
 | 
					     * @type {Transaction}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    this.transaction = transaction
 | 
					    this.transaction = transaction
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @type {Object|null}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    this._changes = null
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -65,6 +72,113 @@ export class YEvent {
 | 
				
			|||||||
  adds (struct) {
 | 
					  adds (struct) {
 | 
				
			||||||
    return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
 | 
					    return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @return {{added:Set<Item>,deleted:Set<Item>,delta:Array<{insert:Array<any>}|{delete:number}|{retain:number}>}}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  get changes () {
 | 
				
			||||||
 | 
					    let changes = this._changes
 | 
				
			||||||
 | 
					    if (changes === null) {
 | 
				
			||||||
 | 
					      const target = this.target
 | 
				
			||||||
 | 
					      const added = set.create()
 | 
				
			||||||
 | 
					      const deleted = set.create()
 | 
				
			||||||
 | 
					      /**
 | 
				
			||||||
 | 
					       * @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
 | 
				
			||||||
 | 
					       */
 | 
				
			||||||
 | 
					      const delta = []
 | 
				
			||||||
 | 
					      /**
 | 
				
			||||||
 | 
					       * @type {Map<string,{ action: 'add' | 'update' | 'delete', oldValue: any}>}
 | 
				
			||||||
 | 
					       */
 | 
				
			||||||
 | 
					      const keys = new Map()
 | 
				
			||||||
 | 
					      changes = {
 | 
				
			||||||
 | 
					        added, deleted, delta, keys
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
 | 
				
			||||||
 | 
					      if (changed.has(null)) {
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @type {any}
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        let lastOp = null
 | 
				
			||||||
 | 
					        const packOp = () => {
 | 
				
			||||||
 | 
					          if (lastOp) {
 | 
				
			||||||
 | 
					            delta.push(lastOp)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let item = target._start; item !== null; item = item.right) {
 | 
				
			||||||
 | 
					          if (item.deleted) {
 | 
				
			||||||
 | 
					            if (this.deletes(item)) {
 | 
				
			||||||
 | 
					              if (lastOp === null || lastOp.delete === undefined) {
 | 
				
			||||||
 | 
					                packOp()
 | 
				
			||||||
 | 
					                lastOp = { delete: 0 }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              lastOp.delete += item.length
 | 
				
			||||||
 | 
					              deleted.add(item)
 | 
				
			||||||
 | 
					            } // else nop
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            if (this.adds(item)) {
 | 
				
			||||||
 | 
					              if (lastOp === null || lastOp.insert === undefined) {
 | 
				
			||||||
 | 
					                packOp()
 | 
				
			||||||
 | 
					                lastOp = { insert: [] }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              lastOp.insert = lastOp.insert.concat(item.content.getContent())
 | 
				
			||||||
 | 
					              added.add(item)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              if (lastOp === null || lastOp.retain === undefined) {
 | 
				
			||||||
 | 
					                packOp()
 | 
				
			||||||
 | 
					                lastOp = { retain: 0 }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              lastOp.retain += item.length
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (lastOp !== null && lastOp.retain === undefined) {
 | 
				
			||||||
 | 
					          packOp()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      changed.forEach(key => {
 | 
				
			||||||
 | 
					        if (key !== null) {
 | 
				
			||||||
 | 
					          const item = /** @type {Item} */ (target._map.get(key))
 | 
				
			||||||
 | 
					          /**
 | 
				
			||||||
 | 
					           * @type {'delete' | 'add' | 'update'}
 | 
				
			||||||
 | 
					           */
 | 
				
			||||||
 | 
					          let action
 | 
				
			||||||
 | 
					          let oldValue
 | 
				
			||||||
 | 
					          if (this.adds(item)) {
 | 
				
			||||||
 | 
					            let prev = item.left
 | 
				
			||||||
 | 
					            while (prev !== null && this.adds(prev)) {
 | 
				
			||||||
 | 
					              prev = prev.left
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (this.deletes(item)) {
 | 
				
			||||||
 | 
					              if (prev !== null && this.deletes(prev)) {
 | 
				
			||||||
 | 
					                action = 'delete'
 | 
				
			||||||
 | 
					                oldValue = array.last(prev.content.getContent())
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              if (prev !== null && this.deletes(prev)) {
 | 
				
			||||||
 | 
					                action = 'update'
 | 
				
			||||||
 | 
					                oldValue = array.last(prev.content.getContent())
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                action = 'add'
 | 
				
			||||||
 | 
					                oldValue = undefined
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            if (this.deletes(item)) {
 | 
				
			||||||
 | 
					              action = 'delete'
 | 
				
			||||||
 | 
					              oldValue = array.last(/** @type {Item} */ item.content.getContent())
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return // nop
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          keys.set(key, { action, oldValue })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      this._changes = changes
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return changes
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import {
 | 
				
			|||||||
  readID,
 | 
					  readID,
 | 
				
			||||||
  getState,
 | 
					  getState,
 | 
				
			||||||
  getStateVector,
 | 
					  getStateVector,
 | 
				
			||||||
  readDeleteSet,
 | 
					  readAndApplyDeleteSet,
 | 
				
			||||||
  writeDeleteSet,
 | 
					  writeDeleteSet,
 | 
				
			||||||
  createDeleteSetFromStructStore,
 | 
					  createDeleteSetFromStructStore,
 | 
				
			||||||
  Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
 | 
					  Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
 | 
				
			||||||
@ -230,7 +230,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => {
 | 
				
			|||||||
  const pendingReaders = store.pendingDeleteReaders
 | 
					  const pendingReaders = store.pendingDeleteReaders
 | 
				
			||||||
  store.pendingDeleteReaders = []
 | 
					  store.pendingDeleteReaders = []
 | 
				
			||||||
  for (let i = 0; i < pendingReaders.length; i++) {
 | 
					  for (let i = 0; i < pendingReaders.length; i++) {
 | 
				
			||||||
    readDeleteSet(pendingReaders[i], transaction, store)
 | 
					    readAndApplyDeleteSet(pendingReaders[i], transaction, store)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -301,7 +301,7 @@ export const readStructs = (decoder, transaction, store) => {
 | 
				
			|||||||
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
 | 
					export const readUpdate = (decoder, ydoc, transactionOrigin) =>
 | 
				
			||||||
  ydoc.transact(transaction => {
 | 
					  ydoc.transact(transaction => {
 | 
				
			||||||
    readStructs(decoder, transaction, ydoc.store)
 | 
					    readStructs(decoder, transaction, ydoc.store)
 | 
				
			||||||
    readDeleteSet(decoder, transaction, ydoc.store)
 | 
					    readAndApplyDeleteSet(decoder, transaction, ydoc.store)
 | 
				
			||||||
  }, transactionOrigin)
 | 
					  }, transactionOrigin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -381,6 +381,22 @@ export const readStateVector = decoder => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState))
 | 
					export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Write State Vector to `lib0/encoding.js#Encoder`.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {encoding.Encoder} encoder
 | 
				
			||||||
 | 
					 * @param {Map<number,number>} sv
 | 
				
			||||||
 | 
					 * @function
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const writeStateVector = (encoder, sv) => {
 | 
				
			||||||
 | 
					  encoding.writeVarUint(encoder, sv.size)
 | 
				
			||||||
 | 
					  sv.forEach((clock, client) => {
 | 
				
			||||||
 | 
					    encoding.writeVarUint(encoder, client)
 | 
				
			||||||
 | 
					    encoding.writeVarUint(encoder, clock)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  return encoder
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Write State Vector to `lib0/encoding.js#Encoder`.
 | 
					 * Write State Vector to `lib0/encoding.js#Encoder`.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@ -389,16 +405,7 @@ export const decodeStateVector = decodedState => readStateVector(decoding.create
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @function
 | 
					 * @function
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const writeDocumentStateVector = (encoder, doc) => {
 | 
					export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encoder, getStateVector(doc.store))
 | 
				
			||||||
  encoding.writeVarUint(encoder, doc.store.clients.size)
 | 
					 | 
				
			||||||
  doc.store.clients.forEach((structs, client) => {
 | 
					 | 
				
			||||||
    const struct = structs[structs.length - 1]
 | 
					 | 
				
			||||||
    const id = struct.id
 | 
					 | 
				
			||||||
    encoding.writeVarUint(encoder, id.client)
 | 
					 | 
				
			||||||
    encoding.writeVarUint(encoder, id.clock + struct.length)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  return encoder
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Encode State as Uint8Array.
 | 
					 * Encode State as Uint8Array.
 | 
				
			||||||
 | 
				
			|||||||
@ -191,6 +191,33 @@ export const testInsertAndDeleteEventsForTypes = tc => {
 | 
				
			|||||||
  compare(users)
 | 
					  compare(users)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testChangeEvent = tc => {
 | 
				
			||||||
 | 
					  const { array0, users } = init(tc, { users: 2 })
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {any}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  let changes = null
 | 
				
			||||||
 | 
					  array0.observe(e => {
 | 
				
			||||||
 | 
					    changes = e.changes
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  const newArr = new Y.Array()
 | 
				
			||||||
 | 
					  array0.insert(0, [newArr, 4, 'dtrn'])
 | 
				
			||||||
 | 
					  t.assert(changes !== null && changes.added.size === 2 && changes.deleted.size === 0)
 | 
				
			||||||
 | 
					  t.compare(changes.delta, [{insert: [newArr, 4, 'dtrn']}])
 | 
				
			||||||
 | 
					  changes = null
 | 
				
			||||||
 | 
					  array0.delete(0, 2)
 | 
				
			||||||
 | 
					  t.assert(changes !== null && changes.added.size === 0 && changes.deleted.size === 2)
 | 
				
			||||||
 | 
					  t.compare(changes.delta, [{ delete: 2 }])
 | 
				
			||||||
 | 
					  changes = null
 | 
				
			||||||
 | 
					  array0.insert(1, [0.1])
 | 
				
			||||||
 | 
					  t.assert(changes !== null && changes.added.size === 1 && changes.deleted.size === 0)
 | 
				
			||||||
 | 
					  t.compare(changes.delta, [{ retain: 1 }, { insert: [0.1] }])
 | 
				
			||||||
 | 
					  compare(users)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {t.TestCase} tc
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
				
			|||||||
@ -292,6 +292,54 @@ export const testThrowsAddAndUpdateAndDeleteEvents = tc => {
 | 
				
			|||||||
  compare(users)
 | 
					  compare(users)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testChangeEvent = tc => {
 | 
				
			||||||
 | 
					  const { map0, users } = init(tc, { users: 2 })
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {any}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  let changes = null
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type {any}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  let keyChange = null
 | 
				
			||||||
 | 
					  map0.observe(e => {
 | 
				
			||||||
 | 
					    changes = e.changes
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  map0.set('a', 1)
 | 
				
			||||||
 | 
					  keyChange = changes.keys.get('a')
 | 
				
			||||||
 | 
					  t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
 | 
				
			||||||
 | 
					  map0.set('a', 2)
 | 
				
			||||||
 | 
					  keyChange = changes.keys.get('a')
 | 
				
			||||||
 | 
					  t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 1)
 | 
				
			||||||
 | 
					  users[0].transact(() => {
 | 
				
			||||||
 | 
					    map0.set('a', 3)
 | 
				
			||||||
 | 
					    map0.set('a', 4)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  keyChange = changes.keys.get('a')
 | 
				
			||||||
 | 
					  t.assert(changes !== null && keyChange.action === 'update' && keyChange.oldValue === 2)
 | 
				
			||||||
 | 
					  users[0].transact(() => {
 | 
				
			||||||
 | 
					    map0.set('b', 1)
 | 
				
			||||||
 | 
					    map0.set('b', 2)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  keyChange = changes.keys.get('b')
 | 
				
			||||||
 | 
					  t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
 | 
				
			||||||
 | 
					  users[0].transact(() => {
 | 
				
			||||||
 | 
					    map0.set('c', 1)
 | 
				
			||||||
 | 
					    map0.delete('c')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  t.assert(changes !== null && changes.keys.size === 0)
 | 
				
			||||||
 | 
					  users[0].transact(() => {
 | 
				
			||||||
 | 
					    map0.set('d', 1)
 | 
				
			||||||
 | 
					    map0.set('d', 2)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  keyChange = changes.keys.get('d')
 | 
				
			||||||
 | 
					  t.assert(changes !== null && keyChange.action === 'add' && keyChange.oldValue === undefined)
 | 
				
			||||||
 | 
					  compare(users)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {t.TestCase} tc
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
				
			|||||||
@ -98,7 +98,7 @@ export const testSnapshot = tc => {
 | 
				
			|||||||
  text0.applyDelta([{
 | 
					  text0.applyDelta([{
 | 
				
			||||||
    insert: 'abcd'
 | 
					    insert: 'abcd'
 | 
				
			||||||
  }])
 | 
					  }])
 | 
				
			||||||
  const snapshot1 = Y.createSnapshotFromDoc(doc0)
 | 
					  const snapshot1 = Y.snapshot(doc0)
 | 
				
			||||||
  text0.applyDelta([{
 | 
					  text0.applyDelta([{
 | 
				
			||||||
    retain: 1
 | 
					    retain: 1
 | 
				
			||||||
  }, {
 | 
					  }, {
 | 
				
			||||||
@ -106,7 +106,7 @@ export const testSnapshot = tc => {
 | 
				
			|||||||
  }, {
 | 
					  }, {
 | 
				
			||||||
    delete: 1
 | 
					    delete: 1
 | 
				
			||||||
  }])
 | 
					  }])
 | 
				
			||||||
  const snapshot2 = Y.createSnapshotFromDoc(doc0)
 | 
					  const snapshot2 = Y.snapshot(doc0)
 | 
				
			||||||
  text0.applyDelta([{
 | 
					  text0.applyDelta([{
 | 
				
			||||||
    retain: 2
 | 
					    retain: 2
 | 
				
			||||||
  }, {
 | 
					  }, {
 | 
				
			||||||
@ -140,7 +140,7 @@ export const testSnapshotDeleteAfter = tc => {
 | 
				
			|||||||
  text0.applyDelta([{
 | 
					  text0.applyDelta([{
 | 
				
			||||||
    insert: 'abcd'
 | 
					    insert: 'abcd'
 | 
				
			||||||
  }])
 | 
					  }])
 | 
				
			||||||
  const snapshot1 = Y.createSnapshotFromDoc(doc0)
 | 
					  const snapshot1 = Y.snapshot(doc0)
 | 
				
			||||||
  text0.applyDelta([{
 | 
					  text0.applyDelta([{
 | 
				
			||||||
    retain: 4
 | 
					    retain: 4
 | 
				
			||||||
  }, {
 | 
					  }, {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user