refactored test suites
This commit is contained in:
		
							parent
							
								
									661232f23c
								
							
						
					
					
						commit
						ee133ef334
					
				@ -71,12 +71,13 @@ var polyfills = [
 | 
			
		||||
var concatOrder = [
 | 
			
		||||
  'y.js',
 | 
			
		||||
  'Connector.js',
 | 
			
		||||
  'OperationStore.js',
 | 
			
		||||
  'Database.js',
 | 
			
		||||
  'Transaction.js',
 | 
			
		||||
  'Struct.js',
 | 
			
		||||
  'Utils.js',
 | 
			
		||||
  'OperationStores/RedBlackTree.js',
 | 
			
		||||
  'OperationStores/Memory.js',
 | 
			
		||||
  'OperationStores/IndexedDB.js',
 | 
			
		||||
  'Databases/RedBlackTree.js',
 | 
			
		||||
  'Databases/Memory.js',
 | 
			
		||||
  'Databases/IndexedDB.js',
 | 
			
		||||
  'Connectors/Test.js',
 | 
			
		||||
  'Connectors/WebRTC.js',
 | 
			
		||||
  'Types/Array.js',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										283
									
								
								src/Database.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								src/Database.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,283 @@
 | 
			
		||||
/* global Y */
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  Partial definition of an OperationStore.
 | 
			
		||||
  TODO: name it Database, operation store only holds operations.
 | 
			
		||||
 | 
			
		||||
  A database definition must alse define the following methods:
 | 
			
		||||
  * logTable() (optional)
 | 
			
		||||
    - show relevant information information in a table
 | 
			
		||||
  * requestTransaction(makeGen)
 | 
			
		||||
    - request a transaction
 | 
			
		||||
  * destroy()
 | 
			
		||||
    - destroy the database
 | 
			
		||||
*/
 | 
			
		||||
class AbstractOperationStore {
 | 
			
		||||
  constructor (y, opts) {
 | 
			
		||||
    this.y = y
 | 
			
		||||
    // E.g. this.listenersById[id] : Array<Listener>
 | 
			
		||||
    this.listenersById = {}
 | 
			
		||||
    // Execute the next time a transaction is requested
 | 
			
		||||
    this.listenersByIdExecuteNow = []
 | 
			
		||||
    // A transaction is requested
 | 
			
		||||
    this.listenersByIdRequestPending = false
 | 
			
		||||
    /* To make things more clear, the following naming conventions:
 | 
			
		||||
       * ls : we put this.listenersById on ls
 | 
			
		||||
       * l : Array<Listener>
 | 
			
		||||
       * id : Id (can't use as property name)
 | 
			
		||||
       * sid : String (converted from id via JSON.stringify
 | 
			
		||||
                       so we can use it as a property name)
 | 
			
		||||
 | 
			
		||||
      Always remember to first overwrite
 | 
			
		||||
      a property before you iterate over it!
 | 
			
		||||
    */
 | 
			
		||||
    // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
			
		||||
    // wont be kept in memory.
 | 
			
		||||
    this.initializedTypes = {}
 | 
			
		||||
    this.whenUserIdSetListener = null
 | 
			
		||||
 | 
			
		||||
    this.gc1 = [] // first stage
 | 
			
		||||
    this.gc2 = [] // second stage -> after that, remove the op
 | 
			
		||||
    this.gcTimeout = opts.gcTimeout || 5000
 | 
			
		||||
    var os = this
 | 
			
		||||
    function garbageCollect () {
 | 
			
		||||
      return new Promise((resolve) => {
 | 
			
		||||
        os.requestTransaction(function * () {
 | 
			
		||||
          if (os.y.connector.isSynced) {
 | 
			
		||||
            for (var i in os.gc2) {
 | 
			
		||||
              var oid = os.gc2[i]
 | 
			
		||||
              yield* this.garbageCollectOperation(oid)
 | 
			
		||||
            }
 | 
			
		||||
            os.gc2 = os.gc1
 | 
			
		||||
            os.gc1 = []
 | 
			
		||||
          }
 | 
			
		||||
          if (os.gcTimeout > 0) {
 | 
			
		||||
            os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
 | 
			
		||||
          }
 | 
			
		||||
          resolve()
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    this.garbageCollect = garbageCollect
 | 
			
		||||
    if (this.gcTimeout > 0) {
 | 
			
		||||
      garbageCollect()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  stopGarbageCollector () {
 | 
			
		||||
    var self = this
 | 
			
		||||
    return new Promise(function (resolve) {
 | 
			
		||||
      self.requestTransaction(function * () {
 | 
			
		||||
        var ungc = self.gc1.concat(self.gc2)
 | 
			
		||||
        self.gc1 = []
 | 
			
		||||
        self.gc2 = []
 | 
			
		||||
        for (var i in ungc) {
 | 
			
		||||
          var op = yield* this.getOperation(ungc[i])
 | 
			
		||||
          delete op.gc
 | 
			
		||||
          yield* this.setOperation(op)
 | 
			
		||||
        }
 | 
			
		||||
        resolve()
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  * garbageCollectAfterSync () {
 | 
			
		||||
    this.requestTransaction(function * () {
 | 
			
		||||
      yield* this.os.iterate(this, null, null, function * (op) {
 | 
			
		||||
        if (op.deleted && op.left != null) {
 | 
			
		||||
          var left = yield this.os.find(op.left)
 | 
			
		||||
          this.store.addToGarbageCollector(op, left)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Try to add to GC.
 | 
			
		||||
 | 
			
		||||
    TODO: rename this function
 | 
			
		||||
 | 
			
		||||
    Rulez:
 | 
			
		||||
    * Only gc if this user is online
 | 
			
		||||
    * The most left element in a list must not be gc'd.
 | 
			
		||||
      => There is at least one element in the list
 | 
			
		||||
 | 
			
		||||
    returns true iff op was added to GC
 | 
			
		||||
  */
 | 
			
		||||
  addToGarbageCollector (op, left) {
 | 
			
		||||
    if (
 | 
			
		||||
      op.gc == null &&
 | 
			
		||||
      op.deleted === true &&
 | 
			
		||||
      this.y.connector.isSynced &&
 | 
			
		||||
      left != null &&
 | 
			
		||||
      left.deleted === true
 | 
			
		||||
    ) {
 | 
			
		||||
      op.gc = true
 | 
			
		||||
      this.gc1.push(op.id)
 | 
			
		||||
      return true
 | 
			
		||||
    } else {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  removeFromGarbageCollector (op) {
 | 
			
		||||
    function filter (o) {
 | 
			
		||||
      return !Y.utils.compareIds(o, op.id)
 | 
			
		||||
    }
 | 
			
		||||
    this.gc1 = this.gc1.filter(filter)
 | 
			
		||||
    this.gc2 = this.gc2.filter(filter)
 | 
			
		||||
    delete op.gc
 | 
			
		||||
  }
 | 
			
		||||
  destroy () {
 | 
			
		||||
    clearInterval(this.gcInterval)
 | 
			
		||||
    this.gcInterval = null
 | 
			
		||||
  }
 | 
			
		||||
  setUserId (userId) {
 | 
			
		||||
    this.userId = userId
 | 
			
		||||
    this.opClock = 0
 | 
			
		||||
    if (this.whenUserIdSetListener != null) {
 | 
			
		||||
      this.whenUserIdSetListener()
 | 
			
		||||
      this.whenUserIdSetListener = null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  whenUserIdSet (f) {
 | 
			
		||||
    if (this.userId != null) {
 | 
			
		||||
      f()
 | 
			
		||||
    } else {
 | 
			
		||||
      this.whenUserIdSetListener = f
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  getNextOpId () {
 | 
			
		||||
    if (this.userId == null) {
 | 
			
		||||
      throw new Error('OperationStore not yet initialized!')
 | 
			
		||||
    }
 | 
			
		||||
    return [this.userId, this.opClock++]
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Apply a list of operations.
 | 
			
		||||
 | 
			
		||||
    * get a transaction
 | 
			
		||||
    * check whether all Struct.*.requiredOps are in the OS
 | 
			
		||||
    * check if it is an expected op (otherwise wait for it)
 | 
			
		||||
    * check if was deleted, apply a delete operation after op was applied
 | 
			
		||||
  */
 | 
			
		||||
  apply (ops) {
 | 
			
		||||
    for (var key in ops) {
 | 
			
		||||
      var o = ops[key]
 | 
			
		||||
      var required = Y.Struct[o.struct].requiredOps(o)
 | 
			
		||||
      this.whenOperationsExist(required, o)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    op is executed as soon as every operation requested is available.
 | 
			
		||||
    Note that Transaction can (and should) buffer requests.
 | 
			
		||||
  */
 | 
			
		||||
  whenOperationsExist (ids, op) {
 | 
			
		||||
    if (ids.length > 0) {
 | 
			
		||||
      let listener = {
 | 
			
		||||
        op: op,
 | 
			
		||||
        missing: ids.length
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let key in ids) {
 | 
			
		||||
        let id = ids[key]
 | 
			
		||||
        let sid = JSON.stringify(id)
 | 
			
		||||
        let l = this.listenersById[sid]
 | 
			
		||||
        if (l == null) {
 | 
			
		||||
          l = []
 | 
			
		||||
          this.listenersById[sid] = l
 | 
			
		||||
        }
 | 
			
		||||
        l.push(listener)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.listenersByIdExecuteNow.push({
 | 
			
		||||
        op: op
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.listenersByIdRequestPending) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.listenersByIdRequestPending = true
 | 
			
		||||
    var store = this
 | 
			
		||||
 | 
			
		||||
    this.requestTransaction(function * () {
 | 
			
		||||
      var exeNow = store.listenersByIdExecuteNow
 | 
			
		||||
      store.listenersByIdExecuteNow = []
 | 
			
		||||
 | 
			
		||||
      var ls = store.listenersById
 | 
			
		||||
      store.listenersById = {}
 | 
			
		||||
 | 
			
		||||
      store.listenersByIdRequestPending = false
 | 
			
		||||
 | 
			
		||||
      for (let key in exeNow) {
 | 
			
		||||
        let o = exeNow[key].op
 | 
			
		||||
        yield* store.tryExecute.call(this, o)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (var sid in ls) {
 | 
			
		||||
        var l = ls[sid]
 | 
			
		||||
        var id = JSON.parse(sid)
 | 
			
		||||
        if ((yield* this.getOperation(id)) == null) {
 | 
			
		||||
          store.listenersById[sid] = l
 | 
			
		||||
        } else {
 | 
			
		||||
          for (let key in l) {
 | 
			
		||||
            let listener = l[key]
 | 
			
		||||
            let o = listener.op
 | 
			
		||||
            if (--listener.missing === 0) {
 | 
			
		||||
              yield* store.tryExecute.call(this, o)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Actually execute an operation, when all expected operations are available.
 | 
			
		||||
  */
 | 
			
		||||
  * tryExecute (op) {
 | 
			
		||||
    if (op.struct === 'Delete') {
 | 
			
		||||
      yield* Y.Struct.Delete.execute.call(this, op)
 | 
			
		||||
    } else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
 | 
			
		||||
      yield* Y.Struct[op.struct].execute.call(this, op)
 | 
			
		||||
      var next = yield* this.addOperation(op)
 | 
			
		||||
      yield* this.store.operationAdded(this, op, next)
 | 
			
		||||
 | 
			
		||||
      // Delete if DS says this is actually deleted
 | 
			
		||||
      if (yield* this.isDeleted(op.id)) {
 | 
			
		||||
        yield* Y.Struct['Delete'].execute.call(this, {struct: 'Delete', target: op.id})
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // called by a transaction when an operation is added
 | 
			
		||||
  * operationAdded (transaction, op, next) {
 | 
			
		||||
    // increase SS
 | 
			
		||||
    var o = op
 | 
			
		||||
    var state = yield* transaction.getState(op.id[0])
 | 
			
		||||
    while (o != null && o.id[1] === state.clock && op.id[0] === o.id[0]) {
 | 
			
		||||
      // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
 | 
			
		||||
      state.clock++
 | 
			
		||||
      yield* transaction.checkDeleteStoreForState(state)
 | 
			
		||||
      o = next()
 | 
			
		||||
    }
 | 
			
		||||
    yield* transaction.setState(state)
 | 
			
		||||
 | 
			
		||||
    // notify whenOperation listeners (by id)
 | 
			
		||||
    var sid = JSON.stringify(op.id)
 | 
			
		||||
    var l = this.listenersById[sid]
 | 
			
		||||
    delete this.listenersById[sid]
 | 
			
		||||
 | 
			
		||||
    if (l != null) {
 | 
			
		||||
      for (var key in l) {
 | 
			
		||||
        var listener = l[key]
 | 
			
		||||
        if (--listener.missing === 0) {
 | 
			
		||||
          this.whenOperationsExist([], listener.op)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // notify parent, if it has been initialized as a custom type
 | 
			
		||||
    var t = this.initializedTypes[JSON.stringify(op.parent)]
 | 
			
		||||
    if (t != null && !op.deleted) {
 | 
			
		||||
      yield* t._changed(transaction, Y.utils.copyObject(op))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Y.AbstractOperationStore = AbstractOperationStore
 | 
			
		||||
							
								
								
									
										329
									
								
								src/Database.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								src/Database.spec.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,329 @@
 | 
			
		||||
/* global Y, async */
 | 
			
		||||
/* eslint-env browser,jasmine,console */
 | 
			
		||||
var databases = ['Memory']
 | 
			
		||||
for (var database of databases) {
 | 
			
		||||
  describe(`Database (${database})`, function () {
 | 
			
		||||
    var store
 | 
			
		||||
    describe('DeleteStore', function () {
 | 
			
		||||
      describe('Basic', function () {
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
          store = new Y[database](null, {
 | 
			
		||||
            gcTimeout: -1
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
        it('Deleted operation is deleted', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['u1', 10])
 | 
			
		||||
            expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Deleted operation extends other deleted operation', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['u1', 10])
 | 
			
		||||
            yield* this.markDeleted(['u1', 11])
 | 
			
		||||
            expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
            expect(yield* this.isDeleted(['u1', 11])).toBeTruthy()
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Deleted operation extends other deleted operation', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['0', 3])
 | 
			
		||||
            yield* this.markDeleted(['0', 4])
 | 
			
		||||
            yield* this.markDeleted(['0', 2])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #1', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['166', 0])
 | 
			
		||||
            yield* this.markDeleted(['166', 2])
 | 
			
		||||
            yield* this.markDeleted(['166', 0])
 | 
			
		||||
            yield* this.markDeleted(['166', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['166', 2])
 | 
			
		||||
            yield* this.markDeleted(['166', 1])
 | 
			
		||||
            yield* this.markDeleted(['166', 3])
 | 
			
		||||
            yield* this.markGarbageCollected(['166', 3])
 | 
			
		||||
            yield* this.markDeleted(['166', 0])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #2', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['293', 0])
 | 
			
		||||
            yield* this.markDeleted(['291', 2])
 | 
			
		||||
            yield* this.markDeleted(['291', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['293', 0])
 | 
			
		||||
            yield* this.markDeleted(['293', 1])
 | 
			
		||||
            yield* this.markGarbageCollected(['291', 2])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #3', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['581', 0])
 | 
			
		||||
            yield* this.markDeleted(['581', 1])
 | 
			
		||||
            yield* this.markDeleted(['580', 0])
 | 
			
		||||
            yield* this.markDeleted(['580', 0])
 | 
			
		||||
            yield* this.markGarbageCollected(['581', 0])
 | 
			
		||||
            yield* this.markDeleted(['581', 2])
 | 
			
		||||
            yield* this.markDeleted(['580', 1])
 | 
			
		||||
            yield* this.markDeleted(['580', 2])
 | 
			
		||||
            yield* this.markDeleted(['580', 1])
 | 
			
		||||
            yield* this.markDeleted(['580', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['581', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['581', 1])
 | 
			
		||||
            yield* this.markGarbageCollected(['580', 1])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #4', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['544', 0])
 | 
			
		||||
            yield* this.markDeleted(['543', 2])
 | 
			
		||||
            yield* this.markDeleted(['544', 0])
 | 
			
		||||
            yield* this.markDeleted(['543', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['544', 0])
 | 
			
		||||
            yield* this.markDeleted(['545', 1])
 | 
			
		||||
            yield* this.markDeleted(['543', 4])
 | 
			
		||||
            yield* this.markDeleted(['543', 3])
 | 
			
		||||
            yield* this.markDeleted(['544', 1])
 | 
			
		||||
            yield* this.markDeleted(['544', 2])
 | 
			
		||||
            yield* this.markDeleted(['544', 1])
 | 
			
		||||
            yield* this.markDeleted(['544', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['543', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['543', 4])
 | 
			
		||||
            yield* this.markGarbageCollected(['544', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['543', 3])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #5', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
 | 
			
		||||
            yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #6', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.applyDeleteSet({'40': [[0, 3, false]]})
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
 | 
			
		||||
            yield* this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
        it('Debug #7', async(function * (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.markDeleted(['9', 2])
 | 
			
		||||
            yield* this.markDeleted(['11', 2])
 | 
			
		||||
            yield* this.markDeleted(['11', 4])
 | 
			
		||||
            yield* this.markDeleted(['11', 1])
 | 
			
		||||
            yield* this.markDeleted(['9', 4])
 | 
			
		||||
            yield* this.markDeleted(['10', 0])
 | 
			
		||||
            yield* this.markGarbageCollected(['11', 2])
 | 
			
		||||
            yield* this.markDeleted(['11', 2])
 | 
			
		||||
            yield* this.markGarbageCollected(['11', 3])
 | 
			
		||||
            yield* this.markDeleted(['11', 3])
 | 
			
		||||
            yield* this.markDeleted(['11', 3])
 | 
			
		||||
            yield* this.markDeleted(['9', 4])
 | 
			
		||||
            yield* this.markDeleted(['10', 0])
 | 
			
		||||
            yield* this.markGarbageCollected(['11', 1])
 | 
			
		||||
            yield* this.markDeleted(['11', 1])
 | 
			
		||||
            expect(yield* this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        }))
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    describe('OperationStore', function () {
 | 
			
		||||
      describe('Basic Tests', function () {
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
          store = new Y[database](null, {
 | 
			
		||||
            gcTimeout: -1
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
        it('debug #1', function (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield this.os.set({id: [2]})
 | 
			
		||||
            yield this.os.set({id: [0]})
 | 
			
		||||
            yield this.os.delete([2])
 | 
			
		||||
            yield this.os.set({id: [1]})
 | 
			
		||||
            expect(yield this.os.find([0])).not.toBeNull()
 | 
			
		||||
            expect(yield this.os.find([1])).not.toBeNull()
 | 
			
		||||
            expect(yield this.os.find([2])).toBeNull()
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
        it('can add&retrieve 5 elements', function (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield this.os.set({val: 'four', id: [4]})
 | 
			
		||||
            yield this.os.set({val: 'one', id: [1]})
 | 
			
		||||
            yield this.os.set({val: 'three', id: [3]})
 | 
			
		||||
            yield this.os.set({val: 'two', id: [2]})
 | 
			
		||||
            yield this.os.set({val: 'five', id: [5]})
 | 
			
		||||
            expect((yield this.os.find([1])).val).toEqual('one')
 | 
			
		||||
            expect((yield this.os.find([2])).val).toEqual('two')
 | 
			
		||||
            expect((yield this.os.find([3])).val).toEqual('three')
 | 
			
		||||
            expect((yield this.os.find([4])).val).toEqual('four')
 | 
			
		||||
            expect((yield this.os.find([5])).val).toEqual('five')
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
        it('5 elements do not exist anymore after deleting them', function (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield this.os.set({val: 'four', id: [4]})
 | 
			
		||||
            yield this.os.set({val: 'one', id: [1]})
 | 
			
		||||
            yield this.os.set({val: 'three', id: [3]})
 | 
			
		||||
            yield this.os.set({val: 'two', id: [2]})
 | 
			
		||||
            yield this.os.set({val: 'five', id: [5]})
 | 
			
		||||
            yield this.os.delete([4])
 | 
			
		||||
            expect(yield this.os.find([4])).not.toBeTruthy()
 | 
			
		||||
            yield this.os.delete([3])
 | 
			
		||||
            expect(yield this.os.find([3])).not.toBeTruthy()
 | 
			
		||||
            yield this.os.delete([2])
 | 
			
		||||
            expect(yield this.os.find([2])).not.toBeTruthy()
 | 
			
		||||
            yield this.os.delete([1])
 | 
			
		||||
            expect(yield this.os.find([1])).not.toBeTruthy()
 | 
			
		||||
            yield this.os.delete([5])
 | 
			
		||||
            expect(yield this.os.find([5])).not.toBeTruthy()
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
      var numberOfOSTests = 1000
 | 
			
		||||
      describe(`Random Tests - after adding&deleting (0.8/0.2) ${numberOfOSTests} times`, function () {
 | 
			
		||||
        var elements = []
 | 
			
		||||
        beforeAll(function (done) {
 | 
			
		||||
          store = new Y[database](null, {
 | 
			
		||||
            gcTimeout: -1
 | 
			
		||||
          })
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            for (var i = 0; i < numberOfOSTests; i++) {
 | 
			
		||||
              var r = Math.random()
 | 
			
		||||
              if (r < 0.8) {
 | 
			
		||||
                var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
 | 
			
		||||
                if (!(yield this.os.findNode(obj))) {
 | 
			
		||||
                  elements.push(obj)
 | 
			
		||||
                  yield this.os.set({id: obj})
 | 
			
		||||
                }
 | 
			
		||||
              } else if (elements.length > 0) {
 | 
			
		||||
                var elemid = Math.floor(Math.random() * elements.length)
 | 
			
		||||
                var elem = elements[elemid]
 | 
			
		||||
                elements = elements.filter(function (e) {
 | 
			
		||||
                  return !Y.utils.compareIds(e, elem)
 | 
			
		||||
                })
 | 
			
		||||
                yield this.os.delete(elem)
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
        it('can find every object', function (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            for (var id of elements) {
 | 
			
		||||
              expect((yield this.os.find(id)).id).toEqual(id)
 | 
			
		||||
            }
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        it('can find every object with lower bound search', function (done) {
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            for (var id of elements) {
 | 
			
		||||
              expect((yield this.os.findNodeWithLowerBound(id)).val.id).toEqual(id)
 | 
			
		||||
            }
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        it('iterating over a tree with lower bound yields the right amount of results', function (done) {
 | 
			
		||||
          var lowerBound = elements[Math.floor(Math.random() * elements.length)]
 | 
			
		||||
          var expectedResults = elements.filter(function (e, pos) {
 | 
			
		||||
            return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && elements.indexOf(e) === pos
 | 
			
		||||
          }).length
 | 
			
		||||
 | 
			
		||||
          var actualResults = 0
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.os.iterate(this, lowerBound, null, function * (val) {
 | 
			
		||||
              expect(val).toBeDefined()
 | 
			
		||||
              actualResults++
 | 
			
		||||
            })
 | 
			
		||||
            expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        it('iterating over a tree without bounds yield the right amount of results', function (done) {
 | 
			
		||||
          var lowerBound = null
 | 
			
		||||
          var expectedResults = elements.filter(function (e, pos) {
 | 
			
		||||
            return elements.indexOf(e) === pos
 | 
			
		||||
          }).length
 | 
			
		||||
          var actualResults = 0
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.os.iterate(this, lowerBound, null, function * (val) {
 | 
			
		||||
              expect(val).toBeDefined()
 | 
			
		||||
              actualResults++
 | 
			
		||||
            })
 | 
			
		||||
            expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        it('iterating over a tree with upper bound yields the right amount of results', function (done) {
 | 
			
		||||
          var upperBound = elements[Math.floor(Math.random() * elements.length)]
 | 
			
		||||
          var expectedResults = elements.filter(function (e, pos) {
 | 
			
		||||
            return (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
 | 
			
		||||
          }).length
 | 
			
		||||
 | 
			
		||||
          var actualResults = 0
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.os.iterate(this, null, upperBound, function * (val) {
 | 
			
		||||
              expect(val).toBeDefined()
 | 
			
		||||
              actualResults++
 | 
			
		||||
            })
 | 
			
		||||
            expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        it('iterating over a tree with upper and lower bounds yield the right amount of results', function (done) {
 | 
			
		||||
          var b1 = elements[Math.floor(Math.random() * elements.length)]
 | 
			
		||||
          var b2 = elements[Math.floor(Math.random() * elements.length)]
 | 
			
		||||
          var upperBound, lowerBound
 | 
			
		||||
          if (Y.utils.smaller(b1, b2)) {
 | 
			
		||||
            lowerBound = b1
 | 
			
		||||
            upperBound = b2
 | 
			
		||||
          } else {
 | 
			
		||||
            lowerBound = b2
 | 
			
		||||
            upperBound = b1
 | 
			
		||||
          }
 | 
			
		||||
          var expectedResults = elements.filter(function (e, pos) {
 | 
			
		||||
            return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) &&
 | 
			
		||||
              (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
 | 
			
		||||
          }).length
 | 
			
		||||
          var actualResults = 0
 | 
			
		||||
          store.requestTransaction(function * () {
 | 
			
		||||
            yield* this.os.iterate(this, lowerBound, upperBound, function * (val) {
 | 
			
		||||
              expect(val).toBeDefined()
 | 
			
		||||
              actualResults++
 | 
			
		||||
            })
 | 
			
		||||
            expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
            done()
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,9 @@
 | 
			
		||||
/* global Y */
 | 
			
		||||
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
Y.IndexedDB = (function () { // eslint-disable-line
 | 
			
		||||
  class Transaction extends Y.AbstractTransaction { // eslint-disable-line
 | 
			
		||||
Y.IndexedDB = (function () {
 | 
			
		||||
  class Transaction extends Y.AbstractTransaction {
 | 
			
		||||
    constructor (store) {
 | 
			
		||||
      super(store)
 | 
			
		||||
      this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite')
 | 
			
		||||
@ -81,7 +83,7 @@ Y.IndexedDB = (function () { // eslint-disable-line
 | 
			
		||||
      return ops
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  class OperationStore extends Y.AbstractOperationStore { // eslint-disable-line no-undef
 | 
			
		||||
  class OperationStore extends Y.AbstractOperationStore {
 | 
			
		||||
    constructor (y, opts) {
 | 
			
		||||
      super(y, opts)
 | 
			
		||||
      if (opts == null) {
 | 
			
		||||
@ -30,7 +30,7 @@ if (typeof window !== 'undefined' && false) {
 | 
			
		||||
          .toEqual(op)
 | 
			
		||||
        yield* this.removeOperation(['1', 0])
 | 
			
		||||
        expect(yield* this.getOperation(['1', 0]))
 | 
			
		||||
          .toBeUndefined()
 | 
			
		||||
          .toBeNull()
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
@ -38,7 +38,7 @@ if (typeof window !== 'undefined' && false) {
 | 
			
		||||
    it('getOperation(op) returns undefined if op does not exist', function (done) {
 | 
			
		||||
      ob.requestTransaction(function *() {
 | 
			
		||||
        var op = yield* this.getOperation("plzDon'tBeThere")
 | 
			
		||||
        expect(op).toBeUndefined()
 | 
			
		||||
        expect(op).toBeNull()
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
@ -64,7 +64,6 @@ if (typeof window !== 'undefined' && false) {
 | 
			
		||||
        yield* this.setState(s1)
 | 
			
		||||
        yield* this.setState(s2)
 | 
			
		||||
        var sv = yield* this.getStateVector()
 | 
			
		||||
        expect(sv).not.toBeUndefined()
 | 
			
		||||
        expect(sv).toEqual([s1, s2])
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
@ -77,7 +76,6 @@ if (typeof window !== 'undefined' && false) {
 | 
			
		||||
        yield* this.setState(s1)
 | 
			
		||||
        yield* this.setState(s2)
 | 
			
		||||
        var sv = yield* this.getStateSet()
 | 
			
		||||
        expect(sv).not.toBeUndefined()
 | 
			
		||||
        expect(sv).toEqual({
 | 
			
		||||
          '1': 1,
 | 
			
		||||
          '2': 3
 | 
			
		||||
@ -17,10 +17,10 @@ Y.Memory = (function () {
 | 
			
		||||
    constructor (y, opts) {
 | 
			
		||||
      super(y, opts)
 | 
			
		||||
      this.os = new Y.utils.RBTree()
 | 
			
		||||
      this.ss = {}
 | 
			
		||||
      this.ds = new Y.utils.RBTree()
 | 
			
		||||
      this.ss = new Y.utils.RBTree()
 | 
			
		||||
      this.waitingTransactions = []
 | 
			
		||||
      this.transactionInProgress = false
 | 
			
		||||
      this.ds = new DeleteStore()
 | 
			
		||||
    }
 | 
			
		||||
    logTable () {
 | 
			
		||||
      var self = this
 | 
			
		||||
@ -221,7 +221,8 @@ class RBTree {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  find (id) {
 | 
			
		||||
    return this.findNode(id).val
 | 
			
		||||
    var n
 | 
			
		||||
    return (n = this.findNode(id)) ? n.val : null
 | 
			
		||||
  }
 | 
			
		||||
  findNode (id) {
 | 
			
		||||
    if (id == null || id.constructor !== Array) {
 | 
			
		||||
@ -387,7 +388,7 @@ class RBTree {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  add (v) {
 | 
			
		||||
  set (v) {
 | 
			
		||||
    if (v == null || v.id == null || v.id.constructor !== Array) {
 | 
			
		||||
      throw new Error('v is expected to have an id property which is an Array!')
 | 
			
		||||
    }
 | 
			
		||||
@ -410,7 +411,8 @@ class RBTree {
 | 
			
		||||
            p = p.right
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          return null
 | 
			
		||||
          p.val = node.val
 | 
			
		||||
          return p
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this._fixInsert(node)
 | 
			
		||||
@ -57,57 +57,17 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
    })
 | 
			
		||||
    this.tree = this.memory.os
 | 
			
		||||
  })
 | 
			
		||||
  it('can add&retrieve 5 elements', function () {
 | 
			
		||||
    this.tree.add({val: 'four', id: [4]})
 | 
			
		||||
    this.tree.add({val: 'one', id: [1]})
 | 
			
		||||
    this.tree.add({val: 'three', id: [3]})
 | 
			
		||||
    this.tree.add({val: 'two', id: [2]})
 | 
			
		||||
    this.tree.add({val: 'five', id: [5]})
 | 
			
		||||
    expect(this.tree.find([1]).val).toEqual('one')
 | 
			
		||||
    expect(this.tree.find([2]).val).toEqual('two')
 | 
			
		||||
    expect(this.tree.find([3]).val).toEqual('three')
 | 
			
		||||
    expect(this.tree.find([4]).val).toEqual('four')
 | 
			
		||||
    expect(this.tree.find([5]).val).toEqual('five')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('5 elements do not exist anymore after deleting them', function () {
 | 
			
		||||
    this.tree.add({val: 'four', id: [4]})
 | 
			
		||||
    this.tree.add({val: 'one', id: [1]})
 | 
			
		||||
    this.tree.add({val: 'three', id: [3]})
 | 
			
		||||
    this.tree.add({val: 'two', id: [2]})
 | 
			
		||||
    this.tree.add({val: 'five', id: [5]})
 | 
			
		||||
    this.tree.delete([4])
 | 
			
		||||
    expect(this.tree.find([4])).not.toBeTruthy()
 | 
			
		||||
    this.tree.delete([3])
 | 
			
		||||
    expect(this.tree.find([3])).not.toBeTruthy()
 | 
			
		||||
    this.tree.delete([2])
 | 
			
		||||
    expect(this.tree.find([2])).not.toBeTruthy()
 | 
			
		||||
    this.tree.delete([1])
 | 
			
		||||
    expect(this.tree.find([1])).not.toBeTruthy()
 | 
			
		||||
    this.tree.delete([5])
 | 
			
		||||
    expect(this.tree.find([5])).not.toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('debug #1', function () {
 | 
			
		||||
    this.tree.add({id: [2]})
 | 
			
		||||
    this.tree.add({id: [0]})
 | 
			
		||||
    this.tree.delete([2])
 | 
			
		||||
    this.tree.add({id: [1]})
 | 
			
		||||
    expect(this.tree.find([0])).not.toBeUndefined()
 | 
			
		||||
    expect(this.tree.find([1])).not.toBeUndefined()
 | 
			
		||||
    expect(this.tree.find([2])).toBeUndefined()
 | 
			
		||||
  })
 | 
			
		||||
  describe('debug #2', function () {
 | 
			
		||||
    var tree = new Y.utils.RBTree()
 | 
			
		||||
    tree.add({id: [8433]})
 | 
			
		||||
    tree.add({id: [12844]})
 | 
			
		||||
    tree.add({id: [1795]})
 | 
			
		||||
    tree.add({id: [30302]})
 | 
			
		||||
    tree.add({id: [64287]})
 | 
			
		||||
    tree.set({id: [8433]})
 | 
			
		||||
    tree.set({id: [12844]})
 | 
			
		||||
    tree.set({id: [1795]})
 | 
			
		||||
    tree.set({id: [30302]})
 | 
			
		||||
    tree.set({id: [64287]})
 | 
			
		||||
    tree.delete([8433])
 | 
			
		||||
    tree.add({id: [28996]})
 | 
			
		||||
    tree.set({id: [28996]})
 | 
			
		||||
    tree.delete([64287])
 | 
			
		||||
    tree.add({id: [22721]})
 | 
			
		||||
    tree.set({id: [22721]})
 | 
			
		||||
 | 
			
		||||
    itRootNodeIsBlack(tree, [])
 | 
			
		||||
    itBlackHeightOfSubTreesAreEqual(tree, [])
 | 
			
		||||
@ -122,12 +82,14 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
        var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)]
 | 
			
		||||
        if (!tree.findNode(obj)) {
 | 
			
		||||
          elements.push(obj)
 | 
			
		||||
          tree.add({id: obj})
 | 
			
		||||
          tree.set({id: obj})
 | 
			
		||||
        }
 | 
			
		||||
      } else if (elements.length > 0) {
 | 
			
		||||
        var elemid = Math.floor(Math.random() * elements.length)
 | 
			
		||||
        var elem = elements[elemid]
 | 
			
		||||
        elements = elements.filter(function (e) {return !Y.utils.compareIds(e, elem); }); // eslint-disable-line
 | 
			
		||||
        elements = elements.filter(function (e) {
 | 
			
		||||
          return !Y.utils.compareIds(e, elem)
 | 
			
		||||
        })
 | 
			
		||||
        tree.delete(elem)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -157,7 +119,7 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
      var actualResults = 0
 | 
			
		||||
      this.memory.requestTransaction(function * () {
 | 
			
		||||
        yield* tree.iterate(this, lowerBound, null, function * (val) {
 | 
			
		||||
          expect(val).not.toBeUndefined()
 | 
			
		||||
          expect(val).toBeDefined()
 | 
			
		||||
          actualResults++
 | 
			
		||||
        })
 | 
			
		||||
        expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
@ -173,7 +135,7 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
      var actualResults = 0
 | 
			
		||||
      this.memory.requestTransaction(function * () {
 | 
			
		||||
        yield* tree.iterate(this, lowerBound, null, function * (val) {
 | 
			
		||||
          expect(val).not.toBeUndefined()
 | 
			
		||||
          expect(val).toBeDefined()
 | 
			
		||||
          actualResults++
 | 
			
		||||
        })
 | 
			
		||||
        expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
@ -190,7 +152,7 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
      var actualResults = 0
 | 
			
		||||
      this.memory.requestTransaction(function * () {
 | 
			
		||||
        yield* tree.iterate(this, null, upperBound, function * (val) {
 | 
			
		||||
          expect(val).not.toBeUndefined()
 | 
			
		||||
          expect(val).toBeDefined()
 | 
			
		||||
          actualResults++
 | 
			
		||||
        })
 | 
			
		||||
        expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
@ -216,7 +178,7 @@ describe('RedBlack Tree', function () {
 | 
			
		||||
      var actualResults = 0
 | 
			
		||||
      this.memory.requestTransaction(function * () {
 | 
			
		||||
        yield* tree.iterate(this, lowerBound, upperBound, function * (val) {
 | 
			
		||||
          expect(val).not.toBeUndefined()
 | 
			
		||||
          expect(val).toBeDefined()
 | 
			
		||||
          actualResults++
 | 
			
		||||
        })
 | 
			
		||||
        expect(expectedResults).toEqual(actualResults)
 | 
			
		||||
@ -176,7 +176,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
 | 
			
		||||
          var o = yield* this.getOperation([d.id[0], d.id[1] + i])
 | 
			
		||||
          // gc'd or deleted
 | 
			
		||||
          if (d.gc) {
 | 
			
		||||
            expect(o).toBeUndefined()
 | 
			
		||||
            expect(o).toBeNull()
 | 
			
		||||
          } else {
 | 
			
		||||
            expect(o.deleted).toBeTruthy()
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -1,148 +0,0 @@
 | 
			
		||||
/* global Y, async */
 | 
			
		||||
/* eslint-env browser,jasmine,console */
 | 
			
		||||
 | 
			
		||||
describe('Memory', function () {
 | 
			
		||||
  describe('DeleteStore', function () {
 | 
			
		||||
    var store
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
      store = new Y.Memory(null, {
 | 
			
		||||
        name: 'Memory',
 | 
			
		||||
        gcTimeout: -1
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    it('Deleted operation is deleted', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['u1', 10])
 | 
			
		||||
        expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Deleted operation extends other deleted operation', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['u1', 10])
 | 
			
		||||
        yield* this.markDeleted(['u1', 11])
 | 
			
		||||
        expect(yield* this.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
        expect(yield* this.isDeleted(['u1', 11])).toBeTruthy()
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Deleted operation extends other deleted operation', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['0', 3])
 | 
			
		||||
        yield* this.markDeleted(['0', 4])
 | 
			
		||||
        yield* this.markDeleted(['0', 2])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #1', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['166', 0])
 | 
			
		||||
        yield* this.markDeleted(['166', 2])
 | 
			
		||||
        yield* this.markDeleted(['166', 0])
 | 
			
		||||
        yield* this.markDeleted(['166', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['166', 2])
 | 
			
		||||
        yield* this.markDeleted(['166', 1])
 | 
			
		||||
        yield* this.markDeleted(['166', 3])
 | 
			
		||||
        yield* this.markGarbageCollected(['166', 3])
 | 
			
		||||
        yield* this.markDeleted(['166', 0])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #2', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['293', 0])
 | 
			
		||||
        yield* this.markDeleted(['291', 2])
 | 
			
		||||
        yield* this.markDeleted(['291', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['293', 0])
 | 
			
		||||
        yield* this.markDeleted(['293', 1])
 | 
			
		||||
        yield* this.markGarbageCollected(['291', 2])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #3', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['581', 0])
 | 
			
		||||
        yield* this.markDeleted(['581', 1])
 | 
			
		||||
        yield* this.markDeleted(['580', 0])
 | 
			
		||||
        yield* this.markDeleted(['580', 0])
 | 
			
		||||
        yield* this.markGarbageCollected(['581', 0])
 | 
			
		||||
        yield* this.markDeleted(['581', 2])
 | 
			
		||||
        yield* this.markDeleted(['580', 1])
 | 
			
		||||
        yield* this.markDeleted(['580', 2])
 | 
			
		||||
        yield* this.markDeleted(['580', 1])
 | 
			
		||||
        yield* this.markDeleted(['580', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['581', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['581', 1])
 | 
			
		||||
        yield* this.markGarbageCollected(['580', 1])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #4', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['544', 0])
 | 
			
		||||
        yield* this.markDeleted(['543', 2])
 | 
			
		||||
        yield* this.markDeleted(['544', 0])
 | 
			
		||||
        yield* this.markDeleted(['543', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['544', 0])
 | 
			
		||||
        yield* this.markDeleted(['545', 1])
 | 
			
		||||
        yield* this.markDeleted(['543', 4])
 | 
			
		||||
        yield* this.markDeleted(['543', 3])
 | 
			
		||||
        yield* this.markDeleted(['544', 1])
 | 
			
		||||
        yield* this.markDeleted(['544', 2])
 | 
			
		||||
        yield* this.markDeleted(['544', 1])
 | 
			
		||||
        yield* this.markDeleted(['544', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['543', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['543', 4])
 | 
			
		||||
        yield* this.markGarbageCollected(['544', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['543', 3])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #5', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
 | 
			
		||||
        yield* this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #6', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.applyDeleteSet({'40': [[0, 3, false]]})
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
 | 
			
		||||
        yield* this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
    it('Debug #7', async(function * (done) {
 | 
			
		||||
      store.requestTransaction(function * () {
 | 
			
		||||
        yield* this.markDeleted(['9', 2])
 | 
			
		||||
        yield* this.markDeleted(['11', 2])
 | 
			
		||||
        yield* this.markDeleted(['11', 4])
 | 
			
		||||
        yield* this.markDeleted(['11', 1])
 | 
			
		||||
        yield* this.markDeleted(['9', 4])
 | 
			
		||||
        yield* this.markDeleted(['10', 0])
 | 
			
		||||
        yield* this.markGarbageCollected(['11', 2])
 | 
			
		||||
        yield* this.markDeleted(['11', 2])
 | 
			
		||||
        yield* this.markGarbageCollected(['11', 3])
 | 
			
		||||
        yield* this.markDeleted(['11', 3])
 | 
			
		||||
        yield* this.markDeleted(['11', 3])
 | 
			
		||||
        yield* this.markDeleted(['9', 4])
 | 
			
		||||
        yield* this.markDeleted(['10', 0])
 | 
			
		||||
        yield* this.markGarbageCollected(['11', 1])
 | 
			
		||||
        yield* this.markDeleted(['11', 1])
 | 
			
		||||
        expect(yield* this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
 | 
			
		||||
        done()
 | 
			
		||||
      })
 | 
			
		||||
    }))
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -206,14 +206,14 @@ class AbstractTransaction {
 | 
			
		||||
        // un-extend left
 | 
			
		||||
        var newlen = n.val.len - (id[1] - n.val.id[1])
 | 
			
		||||
        n.val.len -= newlen
 | 
			
		||||
        n = yield this.ds.add({id: id, len: newlen, gc: false})
 | 
			
		||||
        n = yield this.ds.set({id: id, len: newlen, gc: false})
 | 
			
		||||
      }
 | 
			
		||||
      // get prev&next before adding a new operation
 | 
			
		||||
      var prev = n.prev()
 | 
			
		||||
      var next = n.next()
 | 
			
		||||
      if (id[1] < n.val.id[1] + n.val.len - 1) {
 | 
			
		||||
        // un-extend right
 | 
			
		||||
        yield this.ds.add({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
 | 
			
		||||
        yield this.ds.set({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
 | 
			
		||||
        n.val.len = 1
 | 
			
		||||
      }
 | 
			
		||||
      // set gc'd
 | 
			
		||||
@ -256,11 +256,11 @@ class AbstractTransaction {
 | 
			
		||||
        n.val.len++
 | 
			
		||||
      } else {
 | 
			
		||||
        // cannot extend left
 | 
			
		||||
        n = yield this.ds.add({id: id, len: 1, gc: false})
 | 
			
		||||
        n = yield this.ds.set({id: id, len: 1, gc: false})
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // cannot extend left
 | 
			
		||||
      n = yield this.ds.add({id: id, len: 1, gc: false})
 | 
			
		||||
      n = yield this.ds.set({id: id, len: 1, gc: false})
 | 
			
		||||
    }
 | 
			
		||||
    // can extend right?
 | 
			
		||||
    var next = n.next()
 | 
			
		||||
@ -488,7 +488,7 @@ class AbstractTransaction {
 | 
			
		||||
    return op
 | 
			
		||||
  }
 | 
			
		||||
  * addOperation (op) {
 | 
			
		||||
    var n = yield this.os.add(op)
 | 
			
		||||
    var n = yield this.os.set(op)
 | 
			
		||||
    return function () {
 | 
			
		||||
      if (n != null) {
 | 
			
		||||
        n = n.next()
 | 
			
		||||
@ -505,10 +505,14 @@ class AbstractTransaction {
 | 
			
		||||
    yield this.os.delete(id)
 | 
			
		||||
  }
 | 
			
		||||
  * setState (state) {
 | 
			
		||||
    this.ss[state.user] = state.clock
 | 
			
		||||
    this.ss.set({
 | 
			
		||||
      id: [state.user],
 | 
			
		||||
      clock: state.clock
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  * getState (user) {
 | 
			
		||||
    var clock = this.ss[user]
 | 
			
		||||
    var n
 | 
			
		||||
    var clock = (n = this.ss.find([user])) == null ? null : n.clock
 | 
			
		||||
    if (clock == null) {
 | 
			
		||||
      clock = 0
 | 
			
		||||
    }
 | 
			
		||||
@ -519,17 +523,20 @@ class AbstractTransaction {
 | 
			
		||||
  }
 | 
			
		||||
  * getStateVector () {
 | 
			
		||||
    var stateVector = []
 | 
			
		||||
    for (var user in this.ss) {
 | 
			
		||||
      var clock = this.ss[user]
 | 
			
		||||
    yield* this.ss.iterate(this, null, null, function * (n) {
 | 
			
		||||
      stateVector.push({
 | 
			
		||||
        user: user,
 | 
			
		||||
        clock: clock
 | 
			
		||||
        user: n.id[0],
 | 
			
		||||
        clock: n.clock
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    })
 | 
			
		||||
    return stateVector
 | 
			
		||||
  }
 | 
			
		||||
  * getStateSet () {
 | 
			
		||||
    return Y.utils.copyObject(this.ss)
 | 
			
		||||
    var ss = {}
 | 
			
		||||
    yield* this.ss.iterate(this, null, null, function * (n) {
 | 
			
		||||
      ss[n.id[0]] = n.clock
 | 
			
		||||
    })
 | 
			
		||||
    return ss
 | 
			
		||||
  }
 | 
			
		||||
  * getOperations (startSS) {
 | 
			
		||||
    // TODO: use bounds here!
 | 
			
		||||
@ -619,284 +626,3 @@ class AbstractTransaction {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Y.AbstractTransaction = AbstractTransaction
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  Partial definition of an OperationStore.
 | 
			
		||||
  TODO: name it Database, operation store only holds operations.
 | 
			
		||||
 | 
			
		||||
  A database definition must alse define the following methods:
 | 
			
		||||
  * logTable() (optional)
 | 
			
		||||
    - show relevant information information in a table
 | 
			
		||||
  * requestTransaction(makeGen)
 | 
			
		||||
    - request a transaction
 | 
			
		||||
  * destroy()
 | 
			
		||||
    - destroy the database
 | 
			
		||||
*/
 | 
			
		||||
class AbstractOperationStore {
 | 
			
		||||
  constructor (y, opts) {
 | 
			
		||||
    this.y = y
 | 
			
		||||
    // E.g. this.listenersById[id] : Array<Listener>
 | 
			
		||||
    this.listenersById = {}
 | 
			
		||||
    // Execute the next time a transaction is requested
 | 
			
		||||
    this.listenersByIdExecuteNow = []
 | 
			
		||||
    // A transaction is requested
 | 
			
		||||
    this.listenersByIdRequestPending = false
 | 
			
		||||
    /* To make things more clear, the following naming conventions:
 | 
			
		||||
       * ls : we put this.listenersById on ls
 | 
			
		||||
       * l : Array<Listener>
 | 
			
		||||
       * id : Id (can't use as property name)
 | 
			
		||||
       * sid : String (converted from id via JSON.stringify
 | 
			
		||||
                       so we can use it as a property name)
 | 
			
		||||
 | 
			
		||||
      Always remember to first overwrite
 | 
			
		||||
      a property before you iterate over it!
 | 
			
		||||
    */
 | 
			
		||||
    // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
			
		||||
    // wont be kept in memory.
 | 
			
		||||
    this.initializedTypes = {}
 | 
			
		||||
    this.whenUserIdSetListener = null
 | 
			
		||||
 | 
			
		||||
    this.gc1 = [] // first stage
 | 
			
		||||
    this.gc2 = [] // second stage -> after that, remove the op
 | 
			
		||||
    this.gcTimeout = opts.gcTimeout || 5000
 | 
			
		||||
    var os = this
 | 
			
		||||
    function garbageCollect () {
 | 
			
		||||
      return new Promise((resolve) => {
 | 
			
		||||
        os.requestTransaction(function * () {
 | 
			
		||||
          if (os.y.connector.isSynced) {
 | 
			
		||||
            for (var i in os.gc2) {
 | 
			
		||||
              var oid = os.gc2[i]
 | 
			
		||||
              yield* this.garbageCollectOperation(oid)
 | 
			
		||||
            }
 | 
			
		||||
            os.gc2 = os.gc1
 | 
			
		||||
            os.gc1 = []
 | 
			
		||||
          }
 | 
			
		||||
          if (os.gcTimeout > 0) {
 | 
			
		||||
            os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
 | 
			
		||||
          }
 | 
			
		||||
          resolve()
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    this.garbageCollect = garbageCollect
 | 
			
		||||
    if (this.gcTimeout > 0) {
 | 
			
		||||
      garbageCollect()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  stopGarbageCollector () {
 | 
			
		||||
    var self = this
 | 
			
		||||
    return new Promise(function (resolve) {
 | 
			
		||||
      self.requestTransaction(function * () {
 | 
			
		||||
        var ungc = self.gc1.concat(self.gc2)
 | 
			
		||||
        self.gc1 = []
 | 
			
		||||
        self.gc2 = []
 | 
			
		||||
        for (var i in ungc) {
 | 
			
		||||
          var op = yield* this.getOperation(ungc[i])
 | 
			
		||||
          delete op.gc
 | 
			
		||||
          yield* this.setOperation(op)
 | 
			
		||||
        }
 | 
			
		||||
        resolve()
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  * garbageCollectAfterSync () {
 | 
			
		||||
    this.requestTransaction(function * () {
 | 
			
		||||
      yield* this.os.iterate(this, null, null, function * (op) {
 | 
			
		||||
        if (op.deleted && op.left != null) {
 | 
			
		||||
          var left = yield this.os.find(op.left)
 | 
			
		||||
          this.store.addToGarbageCollector(op, left)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Try to add to GC.
 | 
			
		||||
 | 
			
		||||
    TODO: rename this function
 | 
			
		||||
 | 
			
		||||
    Rulez:
 | 
			
		||||
    * Only gc if this user is online
 | 
			
		||||
    * The most left element in a list must not be gc'd.
 | 
			
		||||
      => There is at least one element in the list
 | 
			
		||||
 | 
			
		||||
    returns true iff op was added to GC
 | 
			
		||||
  */
 | 
			
		||||
  addToGarbageCollector (op, left) {
 | 
			
		||||
    if (
 | 
			
		||||
      op.gc == null &&
 | 
			
		||||
      op.deleted === true &&
 | 
			
		||||
      this.y.connector.isSynced &&
 | 
			
		||||
      left != null &&
 | 
			
		||||
      left.deleted === true
 | 
			
		||||
    ) {
 | 
			
		||||
      op.gc = true
 | 
			
		||||
      this.gc1.push(op.id)
 | 
			
		||||
      return true
 | 
			
		||||
    } else {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  removeFromGarbageCollector (op) {
 | 
			
		||||
    function filter (o) {
 | 
			
		||||
      return !Y.utils.compareIds(o, op.id)
 | 
			
		||||
    }
 | 
			
		||||
    this.gc1 = this.gc1.filter(filter)
 | 
			
		||||
    this.gc2 = this.gc2.filter(filter)
 | 
			
		||||
    delete op.gc
 | 
			
		||||
  }
 | 
			
		||||
  destroy () {
 | 
			
		||||
    clearInterval(this.gcInterval)
 | 
			
		||||
    this.gcInterval = null
 | 
			
		||||
  }
 | 
			
		||||
  setUserId (userId) {
 | 
			
		||||
    this.userId = userId
 | 
			
		||||
    this.opClock = 0
 | 
			
		||||
    if (this.whenUserIdSetListener != null) {
 | 
			
		||||
      this.whenUserIdSetListener()
 | 
			
		||||
      this.whenUserIdSetListener = null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  whenUserIdSet (f) {
 | 
			
		||||
    if (this.userId != null) {
 | 
			
		||||
      f()
 | 
			
		||||
    } else {
 | 
			
		||||
      this.whenUserIdSetListener = f
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  getNextOpId () {
 | 
			
		||||
    if (this.userId == null) {
 | 
			
		||||
      throw new Error('OperationStore not yet initialized!')
 | 
			
		||||
    }
 | 
			
		||||
    return [this.userId, this.opClock++]
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Apply a list of operations.
 | 
			
		||||
 | 
			
		||||
    * get a transaction
 | 
			
		||||
    * check whether all Struct.*.requiredOps are in the OS
 | 
			
		||||
    * check if it is an expected op (otherwise wait for it)
 | 
			
		||||
    * check if was deleted, apply a delete operation after op was applied
 | 
			
		||||
  */
 | 
			
		||||
  apply (ops) {
 | 
			
		||||
    for (var key in ops) {
 | 
			
		||||
      var o = ops[key]
 | 
			
		||||
      var required = Y.Struct[o.struct].requiredOps(o)
 | 
			
		||||
      this.whenOperationsExist(required, o)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    op is executed as soon as every operation requested is available.
 | 
			
		||||
    Note that Transaction can (and should) buffer requests.
 | 
			
		||||
  */
 | 
			
		||||
  whenOperationsExist (ids, op) {
 | 
			
		||||
    if (ids.length > 0) {
 | 
			
		||||
      let listener = {
 | 
			
		||||
        op: op,
 | 
			
		||||
        missing: ids.length
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let key in ids) {
 | 
			
		||||
        let id = ids[key]
 | 
			
		||||
        let sid = JSON.stringify(id)
 | 
			
		||||
        let l = this.listenersById[sid]
 | 
			
		||||
        if (l == null) {
 | 
			
		||||
          l = []
 | 
			
		||||
          this.listenersById[sid] = l
 | 
			
		||||
        }
 | 
			
		||||
        l.push(listener)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.listenersByIdExecuteNow.push({
 | 
			
		||||
        op: op
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.listenersByIdRequestPending) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.listenersByIdRequestPending = true
 | 
			
		||||
    var store = this
 | 
			
		||||
 | 
			
		||||
    this.requestTransaction(function * () {
 | 
			
		||||
      var exeNow = store.listenersByIdExecuteNow
 | 
			
		||||
      store.listenersByIdExecuteNow = []
 | 
			
		||||
 | 
			
		||||
      var ls = store.listenersById
 | 
			
		||||
      store.listenersById = {}
 | 
			
		||||
 | 
			
		||||
      store.listenersByIdRequestPending = false
 | 
			
		||||
 | 
			
		||||
      for (let key in exeNow) {
 | 
			
		||||
        let o = exeNow[key].op
 | 
			
		||||
        yield* store.tryExecute.call(this, o)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (var sid in ls) {
 | 
			
		||||
        var l = ls[sid]
 | 
			
		||||
        var id = JSON.parse(sid)
 | 
			
		||||
        if ((yield* this.getOperation(id)) == null) {
 | 
			
		||||
          store.listenersById[sid] = l
 | 
			
		||||
        } else {
 | 
			
		||||
          for (let key in l) {
 | 
			
		||||
            let listener = l[key]
 | 
			
		||||
            let o = listener.op
 | 
			
		||||
            if (--listener.missing === 0) {
 | 
			
		||||
              yield* store.tryExecute.call(this, o)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  /*
 | 
			
		||||
    Actually execute an operation, when all expected operations are available.
 | 
			
		||||
  */
 | 
			
		||||
  * tryExecute (op) {
 | 
			
		||||
    if (op.struct === 'Delete') {
 | 
			
		||||
      yield* Y.Struct.Delete.execute.call(this, op)
 | 
			
		||||
    } else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
 | 
			
		||||
      yield* Y.Struct[op.struct].execute.call(this, op)
 | 
			
		||||
      var next = yield* this.addOperation(op)
 | 
			
		||||
      yield* this.store.operationAdded(this, op, next)
 | 
			
		||||
 | 
			
		||||
      // Delete if DS says this is actually deleted
 | 
			
		||||
      if (yield* this.isDeleted(op.id)) {
 | 
			
		||||
        yield* Y.Struct['Delete'].execute.call(this, {struct: 'Delete', target: op.id})
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // called by a transaction when an operation is added
 | 
			
		||||
  * operationAdded (transaction, op, next) {
 | 
			
		||||
    // increase SS
 | 
			
		||||
    var o = op
 | 
			
		||||
    var state = yield* transaction.getState(op.id[0])
 | 
			
		||||
    while (o != null && o.id[1] === state.clock && op.id[0] === o.id[0]) {
 | 
			
		||||
      // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
 | 
			
		||||
      state.clock++
 | 
			
		||||
      yield* transaction.checkDeleteStoreForState(state)
 | 
			
		||||
      o = next()
 | 
			
		||||
    }
 | 
			
		||||
    yield* transaction.setState(state)
 | 
			
		||||
 | 
			
		||||
    // notify whenOperation listeners (by id)
 | 
			
		||||
    var sid = JSON.stringify(op.id)
 | 
			
		||||
    var l = this.listenersById[sid]
 | 
			
		||||
    delete this.listenersById[sid]
 | 
			
		||||
 | 
			
		||||
    if (l != null) {
 | 
			
		||||
      for (var key in l) {
 | 
			
		||||
        var listener = l[key]
 | 
			
		||||
        if (--listener.missing === 0) {
 | 
			
		||||
          this.whenOperationsExist([], listener.op)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // notify parent, if it has been initialized as a custom type
 | 
			
		||||
    var t = this.initializedTypes[JSON.stringify(op.parent)]
 | 
			
		||||
    if (t != null && !op.deleted) {
 | 
			
		||||
      yield* t._changed(transaction, Y.utils.copyObject(op))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
Y.AbstractOperationStore = AbstractOperationStore
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user