implemented module loader for yjs
This commit is contained in:
		
							parent
							
								
									138afe39dc
								
							
						
					
					
						commit
						6dc347642b
					
				@ -21,31 +21,10 @@ module.exports = function (gulp, helperOptions) {
 | 
				
			|||||||
    options.regenerator = false
 | 
					    options.regenerator = false
 | 
				
			||||||
    // TODO: include './node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js'
 | 
					    // TODO: include './node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  var concatOrder = [
 | 
					 | 
				
			||||||
    'y.js',
 | 
					 | 
				
			||||||
    'Connector.js',
 | 
					 | 
				
			||||||
    'Database.js',
 | 
					 | 
				
			||||||
    'Transaction.js',
 | 
					 | 
				
			||||||
    'Struct.js',
 | 
					 | 
				
			||||||
    'Utils.js',
 | 
					 | 
				
			||||||
    'Databases/RedBlackTree.js',
 | 
					 | 
				
			||||||
    'Databases/Memory.js',
 | 
					 | 
				
			||||||
    'Databases/IndexedDB.js',
 | 
					 | 
				
			||||||
    'Connectors/Test.js',
 | 
					 | 
				
			||||||
    'Types/Array.js',
 | 
					 | 
				
			||||||
    'Types/Map.js',
 | 
					 | 
				
			||||||
    'Types/TextBind.js'
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
  var yjsfiles = concatOrder.map(function (f) {
 | 
					 | 
				
			||||||
    return '../yjs/src/' + f
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  var files = {
 | 
					  var files = {
 | 
				
			||||||
    dist: helperOptions.polyfills.concat(helperOptions.files.map(function (f) {
 | 
					    dist: helperOptions.entry,
 | 
				
			||||||
      return 'src/' + f
 | 
					    specs: helperOptions.specs,
 | 
				
			||||||
    })),
 | 
					    src: './src/**/*.js'
 | 
				
			||||||
    test: ['../yjs/src/Helper.spec.js'].concat(yjsfiles).concat(helperOptions.files.map(function (f) {
 | 
					 | 
				
			||||||
      return 'src/' + f
 | 
					 | 
				
			||||||
    }).concat(['src/' + options.testfiles]))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var babelOptions = {
 | 
					  var babelOptions = {
 | 
				
			||||||
@ -54,38 +33,10 @@ module.exports = function (gulp, helperOptions) {
 | 
				
			|||||||
    experimental: true
 | 
					    experimental: true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (options.regenerator) {
 | 
					  if (options.regenerator) {
 | 
				
			||||||
    files.test = helperOptions.polyfills.concat(files.test)
 | 
					    files.specs = helperOptions.polyfills.concat(files.specs)
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    babelOptions.blacklist = 'regenerator'
 | 
					    babelOptions.blacklist = 'regenerator'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // babelOptions.blacklist = 'regenerator'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  gulp.task('dist', ['build:dist'], function () {
 | 
					 | 
				
			||||||
    function createDist (pipe) {
 | 
					 | 
				
			||||||
      return pipe
 | 
					 | 
				
			||||||
        .pipe($.if(options.debug, $.sourcemaps.init({loadMaps: true})))
 | 
					 | 
				
			||||||
        .pipe($.concat(options.targetName))
 | 
					 | 
				
			||||||
        .pipe($.if(!options.debug && options.regenerator, $.uglify()))
 | 
					 | 
				
			||||||
        .pipe($.if(options.debug, $.sourcemaps.write('.')))
 | 
					 | 
				
			||||||
        .pipe(gulp.dest('./dist/'))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var pipe
 | 
					 | 
				
			||||||
    if (options.browserify || true) {
 | 
					 | 
				
			||||||
      var browserify = require('browserify')
 | 
					 | 
				
			||||||
      var source = require('vinyl-source-stream')
 | 
					 | 
				
			||||||
      var buffer = require('vinyl-buffer')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      pipe = browserify({
 | 
					 | 
				
			||||||
        entries: 'build/' + options.targetName,
 | 
					 | 
				
			||||||
        debug: options.debug
 | 
					 | 
				
			||||||
      }).bundle()
 | 
					 | 
				
			||||||
        .pipe(source(options.targetName))
 | 
					 | 
				
			||||||
        .pipe(buffer())
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      pipe = gulp.src('build/' + options.targetName)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return createDist(pipe)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gulp.task('dist', function () {
 | 
					  gulp.task('dist', function () {
 | 
				
			||||||
    var browserify = require('browserify')
 | 
					    var browserify = require('browserify')
 | 
				
			||||||
@ -99,7 +50,7 @@ module.exports = function (gulp, helperOptions) {
 | 
				
			|||||||
      .pipe(source(options.targetName))
 | 
					      .pipe(source(options.targetName))
 | 
				
			||||||
      .pipe(buffer())
 | 
					      .pipe(buffer())
 | 
				
			||||||
      .pipe($.if(options.debug, $.sourcemaps.init({loadMaps: true})))
 | 
					      .pipe($.if(options.debug, $.sourcemaps.init({loadMaps: true})))
 | 
				
			||||||
      .pipe($.concat(options.targetName))
 | 
					      .pipe($.if(!options.debug && options.regenerator, $.babel(babelOptions)))
 | 
				
			||||||
      .pipe($.if(!options.debug && options.regenerator, $.uglify()))
 | 
					      .pipe($.if(!options.debug && options.regenerator, $.uglify()))
 | 
				
			||||||
      .pipe($.if(options.debug, $.sourcemaps.write('.')))
 | 
					      .pipe($.if(options.debug, $.sourcemaps.write('.')))
 | 
				
			||||||
      .pipe(gulp.dest('./dist/'))
 | 
					      .pipe(gulp.dest('./dist/'))
 | 
				
			||||||
@ -108,11 +59,46 @@ module.exports = function (gulp, helperOptions) {
 | 
				
			|||||||
  gulp.task('watch:dist', function (cb) {
 | 
					  gulp.task('watch:dist', function (cb) {
 | 
				
			||||||
    options.debug = true
 | 
					    options.debug = true
 | 
				
			||||||
    runSequence('dist', function () {
 | 
					    runSequence('dist', function () {
 | 
				
			||||||
      gulp.watch(files.dist, ['dist'])
 | 
					      gulp.watch(files.src, ['dist'])
 | 
				
			||||||
      cb()
 | 
					      cb()
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gulp.task('dev:node', ['test'], function () {
 | 
				
			||||||
 | 
					    gulp.watch(files.src, ['test'])
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gulp.task('spec-build', function () {
 | 
				
			||||||
 | 
					    var browserify = require('browserify')
 | 
				
			||||||
 | 
					    var source = require('vinyl-source-stream')
 | 
				
			||||||
 | 
					    var buffer = require('vinyl-buffer')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return browserify({
 | 
				
			||||||
 | 
					      entries: files.specs,
 | 
				
			||||||
 | 
					      debug: options.debug
 | 
				
			||||||
 | 
					    }).bundle()
 | 
				
			||||||
 | 
					      .pipe(source('specs.js'))
 | 
				
			||||||
 | 
					      .pipe(buffer())
 | 
				
			||||||
 | 
					      .pipe($.sourcemaps.init({loadMaps: true}))
 | 
				
			||||||
 | 
					      .pipe($.sourcemaps.write())
 | 
				
			||||||
 | 
					      .pipe(gulp.dest('./build/'))
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gulp.task('dev:browser', ['spec-build'], function () {
 | 
				
			||||||
 | 
					    gulp.watch(files.src, ['spec-build'])
 | 
				
			||||||
 | 
					    return gulp.src('./build/specs.js')
 | 
				
			||||||
 | 
					      .pipe($.jasmineBrowser.specRunner())
 | 
				
			||||||
 | 
					      .pipe($.jasmineBrowser.server({port: options.testport}))
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gulp.task('test', function () {
 | 
				
			||||||
 | 
					    return gulp.src(files.specs)
 | 
				
			||||||
 | 
					      .pipe($.jasmine({
 | 
				
			||||||
 | 
					        verbose: true,
 | 
				
			||||||
 | 
					        includeStuckTrace: true
 | 
				
			||||||
 | 
					      }))
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gulp.task('updateSubmodule', function () {
 | 
					  gulp.task('updateSubmodule', function () {
 | 
				
			||||||
    return gulp.src('./package.json', {read: false})
 | 
					    return gulp.src('./package.json', {read: false})
 | 
				
			||||||
      .pipe($.shell([
 | 
					      .pipe($.shell([
 | 
				
			||||||
@ -168,24 +154,4 @@ module.exports = function (gulp, helperOptions) {
 | 
				
			|||||||
        }))
 | 
					        }))
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					 | 
				
			||||||
  gulp.task('dev:node', ['test'], function () {
 | 
					 | 
				
			||||||
    gulp.watch(files.dist, ['test'])
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  gulp.task('dev:browser', ['watch:build'], function () {
 | 
					 | 
				
			||||||
    return gulp.src(files.test)
 | 
					 | 
				
			||||||
      .pipe($.watch(['build/**/*']))
 | 
					 | 
				
			||||||
      .pipe($.jasmineBrowser.specRunner())
 | 
					 | 
				
			||||||
      .pipe($.jasmineBrowser.server({port: options.testport}))
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  gulp.task('test', function () {
 | 
					 | 
				
			||||||
    console.log(files.test)
 | 
					 | 
				
			||||||
    return gulp.src('./dist/y.js')
 | 
					 | 
				
			||||||
      .pipe($.jasmine({
 | 
					 | 
				
			||||||
        verbose: true,
 | 
					 | 
				
			||||||
        includeStuckTrace: true
 | 
					 | 
				
			||||||
      }))
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								gulpfile.js
									
									
									
									
									
								
							@ -49,23 +49,15 @@ var runSequence = require('run-sequence').use(gulp)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require('./gulpfile.helper.js')(gulp, {
 | 
					require('./gulpfile.helper.js')(gulp, {
 | 
				
			||||||
  polyfills: [],
 | 
					  polyfills: [],
 | 
				
			||||||
  files: [
 | 
					  entry: './src/y.js',
 | 
				
			||||||
    'y.js',
 | 
					 | 
				
			||||||
    'Connector.js',
 | 
					 | 
				
			||||||
    'Database.js',
 | 
					 | 
				
			||||||
    'Transaction.js',
 | 
					 | 
				
			||||||
    'Struct.js',
 | 
					 | 
				
			||||||
    'Utils.js',
 | 
					 | 
				
			||||||
    'Databases/RedBlackTree.js',
 | 
					 | 
				
			||||||
    'Databases/Memory.js',
 | 
					 | 
				
			||||||
    'Databases/IndexedDB.js',
 | 
					 | 
				
			||||||
    'Connectors/Test.js',
 | 
					 | 
				
			||||||
    'Types/Array.js',
 | 
					 | 
				
			||||||
    'Types/Map.js',
 | 
					 | 
				
			||||||
    'Types/TextBind.js'
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  targetName: 'y.js',
 | 
					  targetName: 'y.js',
 | 
				
			||||||
  moduleName: 'yjs'
 | 
					  moduleName: 'yjs',
 | 
				
			||||||
 | 
					  specs: [
 | 
				
			||||||
 | 
					    './src/Databases/RedBlackTree.spec.js',
 | 
				
			||||||
 | 
					    './src/Types/Array.spec.js',
 | 
				
			||||||
 | 
					    './src/Types/Map.spec.js',
 | 
				
			||||||
 | 
					    './src/Database.spec.js'
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task('dev:examples', ['updateSubmodule', 'watch:dist'], function () {
 | 
					gulp.task('dev:examples', ['updateSubmodule', 'watch:dist'], function () {
 | 
				
			||||||
 | 
				
			|||||||
@ -68,6 +68,7 @@
 | 
				
			|||||||
    "run-sequence": "^1.1.4",
 | 
					    "run-sequence": "^1.1.4",
 | 
				
			||||||
    "standard": "^5.2.2",
 | 
					    "standard": "^5.2.2",
 | 
				
			||||||
    "vinyl-buffer": "^1.0.0",
 | 
					    "vinyl-buffer": "^1.0.0",
 | 
				
			||||||
    "vinyl-source-stream": "^1.1.0"
 | 
					    "vinyl-source-stream": "^1.1.0",
 | 
				
			||||||
 | 
					    "watchify": "^3.6.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										593
									
								
								src/Connector.js
									
									
									
									
									
								
							
							
						
						
									
										593
									
								
								src/Connector.js
									
									
									
									
									
								
							@ -1,328 +1,329 @@
 | 
				
			|||||||
/* globals Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AbstractConnector {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  /*
 | 
					  class AbstractConnector {
 | 
				
			||||||
    opts contains the following information:
 | 
					    /*
 | 
				
			||||||
     role : String Role of this client ("master" or "slave")
 | 
					      opts contains the following information:
 | 
				
			||||||
     userId : String Uniquely defines the user.
 | 
					       role : String Role of this client ("master" or "slave")
 | 
				
			||||||
     debug: Boolean Whether to print debug messages (optional)
 | 
					       userId : String Uniquely defines the user.
 | 
				
			||||||
  */
 | 
					       debug: Boolean Whether to print debug messages (optional)
 | 
				
			||||||
  constructor (y, opts) {
 | 
					    */
 | 
				
			||||||
    this.y = y
 | 
					    constructor (y, opts) {
 | 
				
			||||||
    if (opts == null) {
 | 
					      this.y = y
 | 
				
			||||||
      opts = {}
 | 
					      if (opts == null) {
 | 
				
			||||||
    }
 | 
					        opts = {}
 | 
				
			||||||
    if (opts.role == null || opts.role === 'master') {
 | 
					 | 
				
			||||||
      this.role = 'master'
 | 
					 | 
				
			||||||
    } else if (opts.role === 'slave') {
 | 
					 | 
				
			||||||
      this.role = 'slave'
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw new Error("Role must be either 'master' or 'slave'!")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.role = opts.role
 | 
					 | 
				
			||||||
    this.connections = {}
 | 
					 | 
				
			||||||
    this.isSynced = false
 | 
					 | 
				
			||||||
    this.userEventListeners = []
 | 
					 | 
				
			||||||
    this.whenSyncedListeners = []
 | 
					 | 
				
			||||||
    this.currentSyncTarget = null
 | 
					 | 
				
			||||||
    this.syncingClients = []
 | 
					 | 
				
			||||||
    this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
 | 
					 | 
				
			||||||
    this.debug = opts.debug === true
 | 
					 | 
				
			||||||
    this.broadcastedHB = false
 | 
					 | 
				
			||||||
    this.syncStep2 = Promise.resolve()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  reconnect () {
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  disconnect () {
 | 
					 | 
				
			||||||
    this.connections = {}
 | 
					 | 
				
			||||||
    this.isSynced = false
 | 
					 | 
				
			||||||
    this.currentSyncTarget = null
 | 
					 | 
				
			||||||
    this.broadcastedHB = false
 | 
					 | 
				
			||||||
    this.syncingClients = []
 | 
					 | 
				
			||||||
    this.whenSyncedListeners = []
 | 
					 | 
				
			||||||
    return this.y.db.stopGarbageCollector()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  setUserId (userId) {
 | 
					 | 
				
			||||||
    this.userId = userId
 | 
					 | 
				
			||||||
    return this.y.db.setUserId(userId)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  onUserEvent (f) {
 | 
					 | 
				
			||||||
    this.userEventListeners.push(f)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  userLeft (user) {
 | 
					 | 
				
			||||||
    delete this.connections[user]
 | 
					 | 
				
			||||||
    if (user === this.currentSyncTarget) {
 | 
					 | 
				
			||||||
      this.currentSyncTarget = null
 | 
					 | 
				
			||||||
      this.findNextSyncTarget()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.syncingClients = this.syncingClients.filter(function (cli) {
 | 
					 | 
				
			||||||
      return cli !== user
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    for (var f of this.userEventListeners) {
 | 
					 | 
				
			||||||
      f({
 | 
					 | 
				
			||||||
        action: 'userLeft',
 | 
					 | 
				
			||||||
        user: user
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  userJoined (user, role) {
 | 
					 | 
				
			||||||
    if (role == null) {
 | 
					 | 
				
			||||||
      throw new Error('You must specify the role of the joined user!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (this.connections[user] != null) {
 | 
					 | 
				
			||||||
      throw new Error('This user already joined!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.connections[user] = {
 | 
					 | 
				
			||||||
      isSynced: false,
 | 
					 | 
				
			||||||
      role: role
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    for (var f of this.userEventListeners) {
 | 
					 | 
				
			||||||
      f({
 | 
					 | 
				
			||||||
        action: 'userJoined',
 | 
					 | 
				
			||||||
        user: user,
 | 
					 | 
				
			||||||
        role: role
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (this.currentSyncTarget == null) {
 | 
					 | 
				
			||||||
      this.findNextSyncTarget()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // Execute a function _when_ we are connected.
 | 
					 | 
				
			||||||
  // If not connected, wait until connected
 | 
					 | 
				
			||||||
  whenSynced (f) {
 | 
					 | 
				
			||||||
    if (this.isSynced) {
 | 
					 | 
				
			||||||
      f()
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.whenSyncedListeners.push(f)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   returns false, if there is no sync target
 | 
					 | 
				
			||||||
   true otherwise
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  findNextSyncTarget () {
 | 
					 | 
				
			||||||
    if (this.currentSyncTarget != null || this.isSynced) {
 | 
					 | 
				
			||||||
      return // "The current sync has not finished!"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var syncUser = null
 | 
					 | 
				
			||||||
    for (var uid in this.connections) {
 | 
					 | 
				
			||||||
      if (!this.connections[uid].isSynced) {
 | 
					 | 
				
			||||||
        syncUser = uid
 | 
					 | 
				
			||||||
        break
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					      if (opts.role == null || opts.role === 'master') {
 | 
				
			||||||
    if (syncUser != null) {
 | 
					        this.role = 'master'
 | 
				
			||||||
      var conn = this
 | 
					      } else if (opts.role === 'slave') {
 | 
				
			||||||
      this.currentSyncTarget = syncUser
 | 
					        this.role = 'slave'
 | 
				
			||||||
      this.y.db.requestTransaction(function *() {
 | 
					      } else {
 | 
				
			||||||
        conn.send(syncUser, {
 | 
					        throw new Error("Role must be either 'master' or 'slave'!")
 | 
				
			||||||
          type: 'sync step 1',
 | 
					 | 
				
			||||||
          stateSet: yield* this.getStateSet(),
 | 
					 | 
				
			||||||
          deleteSet: yield* this.getDeleteSet()
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.isSynced = true
 | 
					 | 
				
			||||||
      // call when synced listeners
 | 
					 | 
				
			||||||
      for (var f of this.whenSyncedListeners) {
 | 
					 | 
				
			||||||
        f()
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      this.role = opts.role
 | 
				
			||||||
 | 
					      this.connections = {}
 | 
				
			||||||
 | 
					      this.isSynced = false
 | 
				
			||||||
 | 
					      this.userEventListeners = []
 | 
				
			||||||
      this.whenSyncedListeners = []
 | 
					      this.whenSyncedListeners = []
 | 
				
			||||||
      this.y.db.requestTransaction(function *() {
 | 
					      this.currentSyncTarget = null
 | 
				
			||||||
        yield* this.garbageCollectAfterSync()
 | 
					      this.syncingClients = []
 | 
				
			||||||
 | 
					      this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
 | 
				
			||||||
 | 
					      this.debug = opts.debug === true
 | 
				
			||||||
 | 
					      this.broadcastedHB = false
 | 
				
			||||||
 | 
					      this.syncStep2 = Promise.resolve()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    reconnect () {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    disconnect () {
 | 
				
			||||||
 | 
					      this.connections = {}
 | 
				
			||||||
 | 
					      this.isSynced = false
 | 
				
			||||||
 | 
					      this.currentSyncTarget = null
 | 
				
			||||||
 | 
					      this.broadcastedHB = false
 | 
				
			||||||
 | 
					      this.syncingClients = []
 | 
				
			||||||
 | 
					      this.whenSyncedListeners = []
 | 
				
			||||||
 | 
					      return this.y.db.stopGarbageCollector()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setUserId (userId) {
 | 
				
			||||||
 | 
					      this.userId = userId
 | 
				
			||||||
 | 
					      return this.y.db.setUserId(userId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    onUserEvent (f) {
 | 
				
			||||||
 | 
					      this.userEventListeners.push(f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    userLeft (user) {
 | 
				
			||||||
 | 
					      delete this.connections[user]
 | 
				
			||||||
 | 
					      if (user === this.currentSyncTarget) {
 | 
				
			||||||
 | 
					        this.currentSyncTarget = null
 | 
				
			||||||
 | 
					        this.findNextSyncTarget()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.syncingClients = this.syncingClients.filter(function (cli) {
 | 
				
			||||||
 | 
					        return cli !== user
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					      for (var f of this.userEventListeners) {
 | 
				
			||||||
  }
 | 
					        f({
 | 
				
			||||||
  send (uid, message) {
 | 
					          action: 'userLeft',
 | 
				
			||||||
    if (this.debug) {
 | 
					          user: user
 | 
				
			||||||
      console.log(`send ${this.userId} -> ${uid}: ${message.type}`, m) // eslint-disable-line
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
    You received a raw message, and you know that it is intended for Yjs. Then call this function.
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  receiveMessage (sender, m) {
 | 
					 | 
				
			||||||
    if (sender === this.userId) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (this.debug) {
 | 
					 | 
				
			||||||
      console.log(`receive ${sender} -> ${this.userId}: ${m.type}`, JSON.parse(JSON.stringify(m))) // eslint-disable-line
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (m.type === 'sync step 1') {
 | 
					 | 
				
			||||||
      // TODO: make transaction, stream the ops
 | 
					 | 
				
			||||||
      let conn = this
 | 
					 | 
				
			||||||
      this.y.db.requestTransaction(function *() {
 | 
					 | 
				
			||||||
        var currentStateSet = yield* this.getStateSet()
 | 
					 | 
				
			||||||
        yield* this.applyDeleteSet(m.deleteSet)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var ds = yield* this.getDeleteSet()
 | 
					 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateSet)
 | 
					 | 
				
			||||||
        conn.send(sender, {
 | 
					 | 
				
			||||||
          type: 'sync step 2',
 | 
					 | 
				
			||||||
          os: ops,
 | 
					 | 
				
			||||||
          stateSet: currentStateSet,
 | 
					 | 
				
			||||||
          deleteSet: ds
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        if (this.forwardToSyncingClients) {
 | 
					      }
 | 
				
			||||||
          conn.syncingClients.push(sender)
 | 
					    }
 | 
				
			||||||
          setTimeout(function () {
 | 
					    userJoined (user, role) {
 | 
				
			||||||
            conn.syncingClients = conn.syncingClients.filter(function (cli) {
 | 
					      if (role == null) {
 | 
				
			||||||
              return cli !== sender
 | 
					        throw new Error('You must specify the role of the joined user!')
 | 
				
			||||||
            })
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.connections[user] != null) {
 | 
				
			||||||
 | 
					        throw new Error('This user already joined!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.connections[user] = {
 | 
				
			||||||
 | 
					        isSynced: false,
 | 
				
			||||||
 | 
					        role: role
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      for (var f of this.userEventListeners) {
 | 
				
			||||||
 | 
					        f({
 | 
				
			||||||
 | 
					          action: 'userJoined',
 | 
				
			||||||
 | 
					          user: user,
 | 
				
			||||||
 | 
					          role: role
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.currentSyncTarget == null) {
 | 
				
			||||||
 | 
					        this.findNextSyncTarget()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Execute a function _when_ we are connected.
 | 
				
			||||||
 | 
					    // If not connected, wait until connected
 | 
				
			||||||
 | 
					    whenSynced (f) {
 | 
				
			||||||
 | 
					      if (this.isSynced) {
 | 
				
			||||||
 | 
					        f()
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.whenSyncedListeners.push(f)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     returns false, if there is no sync target
 | 
				
			||||||
 | 
					     true otherwise
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    findNextSyncTarget () {
 | 
				
			||||||
 | 
					      if (this.currentSyncTarget != null || this.isSynced) {
 | 
				
			||||||
 | 
					        return // "The current sync has not finished!"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var syncUser = null
 | 
				
			||||||
 | 
					      for (var uid in this.connections) {
 | 
				
			||||||
 | 
					        if (!this.connections[uid].isSynced) {
 | 
				
			||||||
 | 
					          syncUser = uid
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (syncUser != null) {
 | 
				
			||||||
 | 
					        var conn = this
 | 
				
			||||||
 | 
					        this.currentSyncTarget = syncUser
 | 
				
			||||||
 | 
					        this.y.db.requestTransaction(function *() {
 | 
				
			||||||
 | 
					          conn.send(syncUser, {
 | 
				
			||||||
 | 
					            type: 'sync step 1',
 | 
				
			||||||
 | 
					            stateSet: yield* this.getStateSet(),
 | 
				
			||||||
 | 
					            deleteSet: yield* this.getDeleteSet()
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.isSynced = true
 | 
				
			||||||
 | 
					        // call when synced listeners
 | 
				
			||||||
 | 
					        for (var f of this.whenSyncedListeners) {
 | 
				
			||||||
 | 
					          f()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.whenSyncedListeners = []
 | 
				
			||||||
 | 
					        this.y.db.requestTransaction(function *() {
 | 
				
			||||||
 | 
					          yield* this.garbageCollectAfterSync()
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    send (uid, message) {
 | 
				
			||||||
 | 
					      if (this.debug) {
 | 
				
			||||||
 | 
					        console.log(`send ${this.userId} -> ${uid}: ${message.type}`, m) // eslint-disable-line
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					      You received a raw message, and you know that it is intended for Yjs. Then call this function.
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    receiveMessage (sender, m) {
 | 
				
			||||||
 | 
					      if (sender === this.userId) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (this.debug) {
 | 
				
			||||||
 | 
					        console.log(`receive ${sender} -> ${this.userId}: ${m.type}`, JSON.parse(JSON.stringify(m))) // eslint-disable-line
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (m.type === 'sync step 1') {
 | 
				
			||||||
 | 
					        // TODO: make transaction, stream the ops
 | 
				
			||||||
 | 
					        let conn = this
 | 
				
			||||||
 | 
					        this.y.db.requestTransaction(function *() {
 | 
				
			||||||
 | 
					          var currentStateSet = yield* this.getStateSet()
 | 
				
			||||||
 | 
					          yield* this.applyDeleteSet(m.deleteSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          var ds = yield* this.getDeleteSet()
 | 
				
			||||||
 | 
					          var ops = yield* this.getOperations(m.stateSet)
 | 
				
			||||||
 | 
					          conn.send(sender, {
 | 
				
			||||||
 | 
					            type: 'sync step 2',
 | 
				
			||||||
 | 
					            os: ops,
 | 
				
			||||||
 | 
					            stateSet: currentStateSet,
 | 
				
			||||||
 | 
					            deleteSet: ds
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          if (this.forwardToSyncingClients) {
 | 
				
			||||||
 | 
					            conn.syncingClients.push(sender)
 | 
				
			||||||
 | 
					            setTimeout(function () {
 | 
				
			||||||
 | 
					              conn.syncingClients = conn.syncingClients.filter(function (cli) {
 | 
				
			||||||
 | 
					                return cli !== sender
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					              conn.send(sender, {
 | 
				
			||||||
 | 
					                type: 'sync done'
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					            }, conn.syncingClientDuration)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
            conn.send(sender, {
 | 
					            conn.send(sender, {
 | 
				
			||||||
              type: 'sync done'
 | 
					              type: 'sync done'
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
          }, conn.syncingClientDuration)
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					          conn._setSyncedWith(sender)
 | 
				
			||||||
          conn.send(sender, {
 | 
					        })
 | 
				
			||||||
            type: 'sync done'
 | 
					      } else if (m.type === 'sync step 2') {
 | 
				
			||||||
          })
 | 
					        let conn = this
 | 
				
			||||||
        }
 | 
					        var broadcastHB = !this.broadcastedHB
 | 
				
			||||||
        conn._setSyncedWith(sender)
 | 
					        this.broadcastedHB = true
 | 
				
			||||||
      })
 | 
					        var db = this.y.db
 | 
				
			||||||
    } else if (m.type === 'sync step 2') {
 | 
					        this.syncStep2 = new Promise(function (resolve) {
 | 
				
			||||||
      let conn = this
 | 
					 | 
				
			||||||
      var broadcastHB = !this.broadcastedHB
 | 
					 | 
				
			||||||
      this.broadcastedHB = true
 | 
					 | 
				
			||||||
      var db = this.y.db
 | 
					 | 
				
			||||||
      this.syncStep2 = new Promise(function (resolve) {
 | 
					 | 
				
			||||||
        db.requestTransaction(function * () {
 | 
					 | 
				
			||||||
          yield* this.applyDeleteSet(m.deleteSet)
 | 
					 | 
				
			||||||
          this.store.apply(m.os)
 | 
					 | 
				
			||||||
          db.requestTransaction(function * () {
 | 
					          db.requestTransaction(function * () {
 | 
				
			||||||
            var ops = yield* this.getOperations(m.stateSet)
 | 
					            yield* this.applyDeleteSet(m.deleteSet)
 | 
				
			||||||
            if (ops.length > 0) {
 | 
					            this.store.apply(m.os)
 | 
				
			||||||
              m = {
 | 
					            db.requestTransaction(function * () {
 | 
				
			||||||
                type: 'update',
 | 
					              var ops = yield* this.getOperations(m.stateSet)
 | 
				
			||||||
                ops: ops
 | 
					              if (ops.length > 0) {
 | 
				
			||||||
 | 
					                m = {
 | 
				
			||||||
 | 
					                  type: 'update',
 | 
				
			||||||
 | 
					                  ops: ops
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!broadcastHB) { // TODO: consider to broadcast here..
 | 
				
			||||||
 | 
					                  conn.send(sender, m)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  // broadcast only once!
 | 
				
			||||||
 | 
					                  conn.broadcast(m)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              if (!broadcastHB) { // TODO: consider to broadcast here..
 | 
					              resolve()
 | 
				
			||||||
                conn.send(sender, m)
 | 
					            })
 | 
				
			||||||
              } else {
 | 
					 | 
				
			||||||
                // broadcast only once!
 | 
					 | 
				
			||||||
                conn.broadcast(m)
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            resolve()
 | 
					 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      })
 | 
					      } else if (m.type === 'sync done') {
 | 
				
			||||||
    } else if (m.type === 'sync done') {
 | 
					        var self = this
 | 
				
			||||||
      var self = this
 | 
					        this.syncStep2.then(function () {
 | 
				
			||||||
      this.syncStep2.then(function () {
 | 
					          self._setSyncedWith(sender)
 | 
				
			||||||
        self._setSyncedWith(sender)
 | 
					        })
 | 
				
			||||||
      })
 | 
					      } else if (m.type === 'update') {
 | 
				
			||||||
    } else if (m.type === 'update') {
 | 
					        if (this.forwardToSyncingClients) {
 | 
				
			||||||
      if (this.forwardToSyncingClients) {
 | 
					          for (var client of this.syncingClients) {
 | 
				
			||||||
        for (var client of this.syncingClients) {
 | 
					            this.send(client, m)
 | 
				
			||||||
          this.send(client, m)
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this.y.db.apply(m.ops)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.y.db.apply(m.ops)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    _setSyncedWith (user) {
 | 
				
			||||||
  _setSyncedWith (user) {
 | 
					      var conn = this.connections[user]
 | 
				
			||||||
    var conn = this.connections[user]
 | 
					      if (conn != null) {
 | 
				
			||||||
    if (conn != null) {
 | 
					        conn.isSynced = true
 | 
				
			||||||
      conn.isSynced = true
 | 
					      }
 | 
				
			||||||
 | 
					      if (user === this.currentSyncTarget) {
 | 
				
			||||||
 | 
					        this.currentSyncTarget = null
 | 
				
			||||||
 | 
					        this.findNextSyncTarget()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (user === this.currentSyncTarget) {
 | 
					    /*
 | 
				
			||||||
      this.currentSyncTarget = null
 | 
					      Currently, the HB encodes operations as JSON. For the moment I want to keep it
 | 
				
			||||||
      this.findNextSyncTarget()
 | 
					      that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
 | 
				
			||||||
    }
 | 
					      too much overhead. Y is very likely to get changed a lot in the future
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
    Currently, the HB encodes operations as JSON. For the moment I want to keep it
 | 
					 | 
				
			||||||
    that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
 | 
					 | 
				
			||||||
    too much overhead. Y is very likely to get changed a lot in the future
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
 | 
					      Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
 | 
				
			||||||
    we encode the JSON as XML.
 | 
					      we encode the JSON as XML.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    When the HB support encoding as XML, the format should look pretty much like this.
 | 
					      When the HB support encoding as XML, the format should look pretty much like this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    does not support primitive values as array elements
 | 
					      does not support primitive values as array elements
 | 
				
			||||||
    expects an ltx (less than xml) object
 | 
					      expects an ltx (less than xml) object
 | 
				
			||||||
  */
 | 
					    */
 | 
				
			||||||
  parseMessageFromXml (m) {
 | 
					    parseMessageFromXml (m) {
 | 
				
			||||||
    function parseArray (node) {
 | 
					      function parseArray (node) {
 | 
				
			||||||
      for (var n of node.children) {
 | 
					        for (var n of node.children) {
 | 
				
			||||||
        if (n.getAttribute('isArray') === 'true') {
 | 
					          if (n.getAttribute('isArray') === 'true') {
 | 
				
			||||||
          return parseArray(n)
 | 
					            return parseArray(n)
 | 
				
			||||||
        } else {
 | 
					          } else {
 | 
				
			||||||
          return parseObject(n)
 | 
					            return parseObject(n)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      function parseObject (node) {
 | 
				
			||||||
 | 
					        var json = {}
 | 
				
			||||||
 | 
					        for (var attrName in node.attrs) {
 | 
				
			||||||
 | 
					          var value = node.attrs[attrName]
 | 
				
			||||||
 | 
					          var int = parseInt(value, 10)
 | 
				
			||||||
 | 
					          if (isNaN(int) || ('' + int) !== value) {
 | 
				
			||||||
 | 
					            json[attrName] = value
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            json[attrName] = int
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (var n in node.children) {
 | 
				
			||||||
 | 
					          var name = n.name
 | 
				
			||||||
 | 
					          if (n.getAttribute('isArray') === 'true') {
 | 
				
			||||||
 | 
					            json[name] = parseArray(n)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            json[name] = parseObject(n)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return json
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      parseObject(m)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    function parseObject (node) {
 | 
					    /*
 | 
				
			||||||
      var json = {}
 | 
					      encode message in xml
 | 
				
			||||||
      for (var attrName in node.attrs) {
 | 
					      we use string because Strophe only accepts an "xml-string"..
 | 
				
			||||||
        var value = node.attrs[attrName]
 | 
					      So {a:4,b:{c:5}} will look like
 | 
				
			||||||
        var int = parseInt(value, 10)
 | 
					      <y a="4">
 | 
				
			||||||
        if (isNaN(int) || ('' + int) !== value) {
 | 
					        <b c="5"></b>
 | 
				
			||||||
          json[attrName] = value
 | 
					      </y>
 | 
				
			||||||
        } else {
 | 
					      m - ltx element
 | 
				
			||||||
          json[attrName] = int
 | 
					      json - Object
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    encodeMessageToXml (msg, obj) {
 | 
				
			||||||
 | 
					      // attributes is optional
 | 
				
			||||||
 | 
					      function encodeObject (m, json) {
 | 
				
			||||||
 | 
					        for (var name in json) {
 | 
				
			||||||
 | 
					          var value = json[name]
 | 
				
			||||||
 | 
					          if (name == null) {
 | 
				
			||||||
 | 
					            // nop
 | 
				
			||||||
 | 
					          } else if (value.constructor === Object) {
 | 
				
			||||||
 | 
					            encodeObject(m.c(name), value)
 | 
				
			||||||
 | 
					          } else if (value.constructor === Array) {
 | 
				
			||||||
 | 
					            encodeArray(m.c(name), value)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            m.setAttribute(name, value)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      for (var n in node.children) {
 | 
					      function encodeArray (m, array) {
 | 
				
			||||||
        var name = n.name
 | 
					        m.setAttribute('isArray', 'true')
 | 
				
			||||||
        if (n.getAttribute('isArray') === 'true') {
 | 
					        for (var e of array) {
 | 
				
			||||||
          json[name] = parseArray(n)
 | 
					          if (e.constructor === Object) {
 | 
				
			||||||
        } else {
 | 
					            encodeObject(m.c('array-element'), e)
 | 
				
			||||||
          json[name] = parseObject(n)
 | 
					          } else {
 | 
				
			||||||
 | 
					            encodeArray(m.c('array-element'), e)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return json
 | 
					      if (obj.constructor === Object) {
 | 
				
			||||||
    }
 | 
					        encodeObject(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
				
			||||||
    parseObject(m)
 | 
					      } else if (obj.constructor === Array) {
 | 
				
			||||||
  }
 | 
					        encodeArray(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
				
			||||||
  /*
 | 
					      } else {
 | 
				
			||||||
    encode message in xml
 | 
					        throw new Error("I can't encode this json!")
 | 
				
			||||||
    we use string because Strophe only accepts an "xml-string"..
 | 
					 | 
				
			||||||
    So {a:4,b:{c:5}} will look like
 | 
					 | 
				
			||||||
    <y a="4">
 | 
					 | 
				
			||||||
      <b c="5"></b>
 | 
					 | 
				
			||||||
    </y>
 | 
					 | 
				
			||||||
    m - ltx element
 | 
					 | 
				
			||||||
    json - Object
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  encodeMessageToXml (msg, obj) {
 | 
					 | 
				
			||||||
    // attributes is optional
 | 
					 | 
				
			||||||
    function encodeObject (m, json) {
 | 
					 | 
				
			||||||
      for (var name in json) {
 | 
					 | 
				
			||||||
        var value = json[name]
 | 
					 | 
				
			||||||
        if (name == null) {
 | 
					 | 
				
			||||||
          // nop
 | 
					 | 
				
			||||||
        } else if (value.constructor === Object) {
 | 
					 | 
				
			||||||
          encodeObject(m.c(name), value)
 | 
					 | 
				
			||||||
        } else if (value.constructor === Array) {
 | 
					 | 
				
			||||||
          encodeArray(m.c(name), value)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          m.setAttribute(name, value)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    function encodeArray (m, array) {
 | 
					 | 
				
			||||||
      m.setAttribute('isArray', 'true')
 | 
					 | 
				
			||||||
      for (var e of array) {
 | 
					 | 
				
			||||||
        if (e.constructor === Object) {
 | 
					 | 
				
			||||||
          encodeObject(m.c('array-element'), e)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          encodeArray(m.c('array-element'), e)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (obj.constructor === Object) {
 | 
					 | 
				
			||||||
      encodeObject(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
					 | 
				
			||||||
    } else if (obj.constructor === Array) {
 | 
					 | 
				
			||||||
      encodeArray(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw new Error("I can't encode this json!")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  Y.AbstractConnector = AbstractConnector
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
Y.AbstractConnector = AbstractConnector
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,136 +1,138 @@
 | 
				
			|||||||
/* global getRandom, Y, wait, async */
 | 
					/* global getRandom, wait, async */
 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var globalRoom = {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  users: {},
 | 
					  var globalRoom = {
 | 
				
			||||||
  buffers: {},
 | 
					    users: {},
 | 
				
			||||||
  removeUser: function (user) {
 | 
					    buffers: {},
 | 
				
			||||||
    for (var i in this.users) {
 | 
					    removeUser: function (user) {
 | 
				
			||||||
      this.users[i].userLeft(user)
 | 
					      for (var i in this.users) {
 | 
				
			||||||
    }
 | 
					        this.users[i].userLeft(user)
 | 
				
			||||||
    delete this.users[user]
 | 
					 | 
				
			||||||
    delete this.buffers[user]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  addUser: function (connector) {
 | 
					 | 
				
			||||||
    this.users[connector.userId] = connector
 | 
					 | 
				
			||||||
    this.buffers[connector.userId] = []
 | 
					 | 
				
			||||||
    for (var uname in this.users) {
 | 
					 | 
				
			||||||
      if (uname !== connector.userId) {
 | 
					 | 
				
			||||||
        var u = this.users[uname]
 | 
					 | 
				
			||||||
        u.userJoined(connector.userId, 'master')
 | 
					 | 
				
			||||||
        connector.userJoined(u.userId, 'master')
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					      delete this.users[user]
 | 
				
			||||||
  }
 | 
					      delete this.buffers[user]
 | 
				
			||||||
}
 | 
					    },
 | 
				
			||||||
Y.utils.globalRoom = globalRoom
 | 
					    addUser: function (connector) {
 | 
				
			||||||
 | 
					      this.users[connector.userId] = connector
 | 
				
			||||||
function flushOne () {
 | 
					      this.buffers[connector.userId] = []
 | 
				
			||||||
  var bufs = []
 | 
					      for (var uname in this.users) {
 | 
				
			||||||
  for (var i in globalRoom.buffers) {
 | 
					        if (uname !== connector.userId) {
 | 
				
			||||||
    if (globalRoom.buffers[i].length > 0) {
 | 
					          var u = this.users[uname]
 | 
				
			||||||
      bufs.push(i)
 | 
					          u.userJoined(connector.userId, 'master')
 | 
				
			||||||
    }
 | 
					          connector.userJoined(u.userId, 'master')
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (bufs.length > 0) {
 | 
					 | 
				
			||||||
    var userId = getRandom(bufs)
 | 
					 | 
				
			||||||
    var m = globalRoom.buffers[userId].shift()
 | 
					 | 
				
			||||||
    var user = globalRoom.users[userId]
 | 
					 | 
				
			||||||
    user.receiveMessage(m[0], m[1])
 | 
					 | 
				
			||||||
    return true
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// setInterval(flushOne, 10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var userIdCounter = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Test extends Y.AbstractConnector {
 | 
					 | 
				
			||||||
  constructor (y, options) {
 | 
					 | 
				
			||||||
    if (options === undefined) {
 | 
					 | 
				
			||||||
      throw new Error('Options must not be undefined!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    options.role = 'master'
 | 
					 | 
				
			||||||
    options.forwardToSyncingClients = false
 | 
					 | 
				
			||||||
    super(y, options)
 | 
					 | 
				
			||||||
    this.setUserId((userIdCounter++) + '').then(() => {
 | 
					 | 
				
			||||||
      globalRoom.addUser(this)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    this.globalRoom = globalRoom
 | 
					 | 
				
			||||||
    this.syncingClientDuration = 0
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  receiveMessage (sender, m) {
 | 
					 | 
				
			||||||
    super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  send (userId, message) {
 | 
					 | 
				
			||||||
    var buffer = globalRoom.buffers[userId]
 | 
					 | 
				
			||||||
    if (buffer != null) {
 | 
					 | 
				
			||||||
      buffer.push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  broadcast (message) {
 | 
					 | 
				
			||||||
    for (var key in globalRoom.buffers) {
 | 
					 | 
				
			||||||
      globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  isDisconnected () {
 | 
					 | 
				
			||||||
    return globalRoom.users[this.userId] == null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  reconnect () {
 | 
					 | 
				
			||||||
    if (this.isDisconnected()) {
 | 
					 | 
				
			||||||
      globalRoom.addUser(this)
 | 
					 | 
				
			||||||
      super.reconnect()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return this.flushAll()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  disconnect () {
 | 
					 | 
				
			||||||
    if (!this.isDisconnected()) {
 | 
					 | 
				
			||||||
      globalRoom.removeUser(this.userId)
 | 
					 | 
				
			||||||
      super.disconnect()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return wait()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  flush () {
 | 
					 | 
				
			||||||
    var self = this
 | 
					 | 
				
			||||||
    return async(function * () {
 | 
					 | 
				
			||||||
      yield wait()
 | 
					 | 
				
			||||||
      while (globalRoom.buffers[self.userId].length > 0) {
 | 
					 | 
				
			||||||
        var m = globalRoom.buffers[self.userId].shift()
 | 
					 | 
				
			||||||
        this.receiveMessage(m[0], m[1])
 | 
					 | 
				
			||||||
        yield wait()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  flushAll () {
 | 
					 | 
				
			||||||
    return new Promise(function (resolve) {
 | 
					 | 
				
			||||||
      // flushes may result in more created operations,
 | 
					 | 
				
			||||||
      // flush until there is nothing more to flush
 | 
					 | 
				
			||||||
      function nextFlush () {
 | 
					 | 
				
			||||||
        var c = flushOne()
 | 
					 | 
				
			||||||
        if (c) {
 | 
					 | 
				
			||||||
          while (flushOne()) {
 | 
					 | 
				
			||||||
            // nop
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          wait().then(nextFlush)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          wait().then(function () {
 | 
					 | 
				
			||||||
            resolve()
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // in the case that there are
 | 
					    }
 | 
				
			||||||
      // still actions that want to be performed
 | 
					 | 
				
			||||||
      wait().then(nextFlush)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  /*
 | 
					  Y.utils.globalRoom = globalRoom
 | 
				
			||||||
    Flushes an operation for some user..
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  flushOne () {
 | 
					 | 
				
			||||||
    flushOne()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.Test = Test
 | 
					  function flushOne () {
 | 
				
			||||||
 | 
					    var bufs = []
 | 
				
			||||||
 | 
					    for (var i in globalRoom.buffers) {
 | 
				
			||||||
 | 
					      if (globalRoom.buffers[i].length > 0) {
 | 
				
			||||||
 | 
					        bufs.push(i)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (bufs.length > 0) {
 | 
				
			||||||
 | 
					      var userId = getRandom(bufs)
 | 
				
			||||||
 | 
					      var m = globalRoom.buffers[userId].shift()
 | 
				
			||||||
 | 
					      var user = globalRoom.users[userId]
 | 
				
			||||||
 | 
					      user.receiveMessage(m[0], m[1])
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // setInterval(flushOne, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var userIdCounter = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class Test extends Y.AbstractConnector {
 | 
				
			||||||
 | 
					    constructor (y, options) {
 | 
				
			||||||
 | 
					      if (options === undefined) {
 | 
				
			||||||
 | 
					        throw new Error('Options must not be undefined!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      options.role = 'master'
 | 
				
			||||||
 | 
					      options.forwardToSyncingClients = false
 | 
				
			||||||
 | 
					      super(y, options)
 | 
				
			||||||
 | 
					      this.setUserId((userIdCounter++) + '').then(() => {
 | 
				
			||||||
 | 
					        globalRoom.addUser(this)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      this.globalRoom = globalRoom
 | 
				
			||||||
 | 
					      this.syncingClientDuration = 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    receiveMessage (sender, m) {
 | 
				
			||||||
 | 
					      super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    send (userId, message) {
 | 
				
			||||||
 | 
					      var buffer = globalRoom.buffers[userId]
 | 
				
			||||||
 | 
					      if (buffer != null) {
 | 
				
			||||||
 | 
					        buffer.push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    broadcast (message) {
 | 
				
			||||||
 | 
					      for (var key in globalRoom.buffers) {
 | 
				
			||||||
 | 
					        globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    isDisconnected () {
 | 
				
			||||||
 | 
					      return globalRoom.users[this.userId] == null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    reconnect () {
 | 
				
			||||||
 | 
					      if (this.isDisconnected()) {
 | 
				
			||||||
 | 
					        globalRoom.addUser(this)
 | 
				
			||||||
 | 
					        super.reconnect()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return this.flushAll()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    disconnect () {
 | 
				
			||||||
 | 
					      if (!this.isDisconnected()) {
 | 
				
			||||||
 | 
					        globalRoom.removeUser(this.userId)
 | 
				
			||||||
 | 
					        super.disconnect()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return wait()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    flush () {
 | 
				
			||||||
 | 
					      var self = this
 | 
				
			||||||
 | 
					      return async(function * () {
 | 
				
			||||||
 | 
					        yield wait()
 | 
				
			||||||
 | 
					        while (globalRoom.buffers[self.userId].length > 0) {
 | 
				
			||||||
 | 
					          var m = globalRoom.buffers[self.userId].shift()
 | 
				
			||||||
 | 
					          this.receiveMessage(m[0], m[1])
 | 
				
			||||||
 | 
					          yield wait()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    flushAll () {
 | 
				
			||||||
 | 
					      return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					        // flushes may result in more created operations,
 | 
				
			||||||
 | 
					        // flush until there is nothing more to flush
 | 
				
			||||||
 | 
					        function nextFlush () {
 | 
				
			||||||
 | 
					          var c = flushOne()
 | 
				
			||||||
 | 
					          if (c) {
 | 
				
			||||||
 | 
					            while (flushOne()) {
 | 
				
			||||||
 | 
					              // nop
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            wait().then(nextFlush)
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            wait().then(function () {
 | 
				
			||||||
 | 
					              resolve()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // in the case that there are
 | 
				
			||||||
 | 
					        // still actions that want to be performed
 | 
				
			||||||
 | 
					        wait().then(nextFlush)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					      Flushes an operation for some user..
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    flushOne () {
 | 
				
			||||||
 | 
					      flushOne()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Y.Test = Test
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										607
									
								
								src/Database.js
									
									
									
									
									
								
							
							
						
						
									
										607
									
								
								src/Database.js
									
									
									
									
									
								
							@ -1,341 +1,342 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  Partial definition of an OperationStore.
 | 
					  /*
 | 
				
			||||||
  TODO: name it Database, operation store only holds operations.
 | 
					    Partial definition of an OperationStore.
 | 
				
			||||||
 | 
					    TODO: name it Database, operation store only holds operations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  A database definition must alse define the following methods:
 | 
					    A database definition must alse define the following methods:
 | 
				
			||||||
  * logTable() (optional)
 | 
					    * logTable() (optional)
 | 
				
			||||||
    - show relevant information information in a table
 | 
					      - show relevant information information in a table
 | 
				
			||||||
  * requestTransaction(makeGen)
 | 
					    * requestTransaction(makeGen)
 | 
				
			||||||
    - request a transaction
 | 
					      - request a transaction
 | 
				
			||||||
  * destroy()
 | 
					    * destroy()
 | 
				
			||||||
    - destroy the database
 | 
					      - destroy the database
 | 
				
			||||||
*/
 | 
					  */
 | 
				
			||||||
class AbstractDatabase {
 | 
					  class AbstractDatabase {
 | 
				
			||||||
  constructor (y, opts) {
 | 
					    constructor (y, opts) {
 | 
				
			||||||
    this.y = y
 | 
					      this.y = y
 | 
				
			||||||
    // E.g. this.listenersById[id] : Array<Listener>
 | 
					      // E.g. this.listenersById[id] : Array<Listener>
 | 
				
			||||||
    this.listenersById = {}
 | 
					      this.listenersById = {}
 | 
				
			||||||
    // Execute the next time a transaction is requested
 | 
					      // Execute the next time a transaction is requested
 | 
				
			||||||
    this.listenersByIdExecuteNow = []
 | 
					      this.listenersByIdExecuteNow = []
 | 
				
			||||||
    // A transaction is requested
 | 
					      // A transaction is requested
 | 
				
			||||||
    this.listenersByIdRequestPending = false
 | 
					      this.listenersByIdRequestPending = false
 | 
				
			||||||
    /* To make things more clear, the following naming conventions:
 | 
					      /* To make things more clear, the following naming conventions:
 | 
				
			||||||
       * ls : we put this.listenersById on ls
 | 
					         * ls : we put this.listenersById on ls
 | 
				
			||||||
       * l : Array<Listener>
 | 
					         * l : Array<Listener>
 | 
				
			||||||
       * id : Id (can't use as property name)
 | 
					         * id : Id (can't use as property name)
 | 
				
			||||||
       * sid : String (converted from id via JSON.stringify
 | 
					         * sid : String (converted from id via JSON.stringify
 | 
				
			||||||
                       so we can use it as a property name)
 | 
					                         so we can use it as a property name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Always remember to first overwrite
 | 
					        Always remember to first overwrite
 | 
				
			||||||
      a property before you iterate over it!
 | 
					        a property before you iterate over it!
 | 
				
			||||||
    */
 | 
					      */
 | 
				
			||||||
    // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
					      // TODO: Use ES7 Weak Maps. This way types that are no longer user,
 | 
				
			||||||
    // wont be kept in memory.
 | 
					      // wont be kept in memory.
 | 
				
			||||||
    this.initializedTypes = {}
 | 
					      this.initializedTypes = {}
 | 
				
			||||||
    this.whenUserIdSetListener = null
 | 
					      this.whenUserIdSetListener = null
 | 
				
			||||||
    this.waitingTransactions = []
 | 
					      this.waitingTransactions = []
 | 
				
			||||||
    this.transactionInProgress = false
 | 
					      this.transactionInProgress = false
 | 
				
			||||||
    if (typeof YConcurrency_TestingMode !== 'undefined') {
 | 
					      if (typeof YConcurrency_TestingMode !== 'undefined') {
 | 
				
			||||||
      this.executeOrder = []
 | 
					        this.executeOrder = []
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
    this.gc1 = [] // first stage
 | 
					      this.gc1 = [] // first stage
 | 
				
			||||||
    this.gc2 = [] // second stage -> after that, remove the op
 | 
					      this.gc2 = [] // second stage -> after that, remove the op
 | 
				
			||||||
    this.gcTimeout = opts.gcTimeout || 5000
 | 
					      this.gcTimeout = opts.gcTimeout || 5000
 | 
				
			||||||
    var os = this
 | 
					      var os = this
 | 
				
			||||||
    function garbageCollect () {
 | 
					      function garbageCollect () {
 | 
				
			||||||
      return new Promise((resolve) => {
 | 
					        return new Promise((resolve) => {
 | 
				
			||||||
        os.requestTransaction(function * () {
 | 
					          os.requestTransaction(function * () {
 | 
				
			||||||
          if (os.y.connector != null && os.y.connector.isSynced) {
 | 
					            if (os.y.connector != null && os.y.connector.isSynced) {
 | 
				
			||||||
            for (var i in os.gc2) {
 | 
					              for (var i in os.gc2) {
 | 
				
			||||||
              var oid = os.gc2[i]
 | 
					                var oid = os.gc2[i]
 | 
				
			||||||
              yield* this.garbageCollectOperation(oid)
 | 
					                yield* this.garbageCollectOperation(oid)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              os.gc2 = os.gc1
 | 
				
			||||||
 | 
					              os.gc1 = []
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            os.gc2 = os.gc1
 | 
					            if (os.gcTimeout > 0) {
 | 
				
			||||||
            os.gc1 = []
 | 
					              os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            resolve()
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.garbageCollect = garbageCollect
 | 
				
			||||||
 | 
					      if (this.gcTimeout > 0) {
 | 
				
			||||||
 | 
					        garbageCollect()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    addToDebug () {
 | 
				
			||||||
 | 
					      if (typeof YConcurrency_TestingMode !== 'undefined') {
 | 
				
			||||||
 | 
					        var command = Array.prototype.map.call(arguments, function (s) {
 | 
				
			||||||
 | 
					          if (typeof s === 'string') {
 | 
				
			||||||
 | 
					            return s
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return JSON.stringify(s)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (os.gcTimeout > 0) {
 | 
					        }).join('').replace(/"/g, "'").replace(/,/g, ', ').replace(/:/g, ': ')
 | 
				
			||||||
            os.gcInterval = setTimeout(garbageCollect, os.gcTimeout)
 | 
					        this.executeOrder.push(command)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    getDebugData () {
 | 
				
			||||||
 | 
					      console.log(this.executeOrder.join('\n'))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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()
 | 
					          resolve()
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.garbageCollect = garbageCollect
 | 
					    /*
 | 
				
			||||||
    if (this.gcTimeout > 0) {
 | 
					      Try to add to GC.
 | 
				
			||||||
      garbageCollect()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  addToDebug () {
 | 
					 | 
				
			||||||
    if (typeof YConcurrency_TestingMode !== 'undefined') {
 | 
					 | 
				
			||||||
      var command = Array.prototype.map.call(arguments, function (s) {
 | 
					 | 
				
			||||||
        if (typeof s === 'string') {
 | 
					 | 
				
			||||||
          return s
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return JSON.stringify(s)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }).join('').replace(/"/g, "'").replace(/,/g, ', ').replace(/:/g, ': ')
 | 
					 | 
				
			||||||
      this.executeOrder.push(command)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  getDebugData () {
 | 
					 | 
				
			||||||
    console.log(this.executeOrder.join('\n'))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  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()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
    Try to add to GC.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TODO: rename this function
 | 
					      TODO: rename this function
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Rulez:
 | 
					      Rulez:
 | 
				
			||||||
    * Only gc if this user is online
 | 
					      * Only gc if this user is online
 | 
				
			||||||
    * The most left element in a list must not be gc'd.
 | 
					      * The most left element in a list must not be gc'd.
 | 
				
			||||||
      => There is at least one element in the list
 | 
					        => There is at least one element in the list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    returns true iff op was added to GC
 | 
					      returns true iff op was added to GC
 | 
				
			||||||
  */
 | 
					    */
 | 
				
			||||||
  addToGarbageCollector (op, left) {
 | 
					    addToGarbageCollector (op, left) {
 | 
				
			||||||
    if (
 | 
					      if (
 | 
				
			||||||
      op.gc == null &&
 | 
					        op.gc == null &&
 | 
				
			||||||
      op.deleted === true &&
 | 
					        op.deleted === true &&
 | 
				
			||||||
      this.y.connector.isSynced &&
 | 
					        this.y.connector.isSynced &&
 | 
				
			||||||
      left != null &&
 | 
					        left != null &&
 | 
				
			||||||
      left.deleted === true
 | 
					        left.deleted === true
 | 
				
			||||||
    ) {
 | 
					      ) {
 | 
				
			||||||
      op.gc = true
 | 
					        op.gc = true
 | 
				
			||||||
      this.gc1.push(op.id)
 | 
					        this.gc1.push(op.id)
 | 
				
			||||||
      return true
 | 
					        return true
 | 
				
			||||||
    } else {
 | 
					      } else {
 | 
				
			||||||
      return false
 | 
					        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) {
 | 
					 | 
				
			||||||
    var self = this
 | 
					 | 
				
			||||||
    return new Promise(function (resolve) {
 | 
					 | 
				
			||||||
      self.requestTransaction(function * () {
 | 
					 | 
				
			||||||
        self.userId = userId
 | 
					 | 
				
			||||||
        self.opClock = (yield* this.getState(userId)).clock
 | 
					 | 
				
			||||||
        if (self.whenUserIdSetListener != null) {
 | 
					 | 
				
			||||||
          self.whenUserIdSetListener()
 | 
					 | 
				
			||||||
          self.whenUserIdSetListener = null
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        resolve()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  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) {
 | 
					    removeFromGarbageCollector (op) {
 | 
				
			||||||
        let id = ids[key]
 | 
					      function filter (o) {
 | 
				
			||||||
        let sid = JSON.stringify(id)
 | 
					        return !Y.utils.compareIds(o, op.id)
 | 
				
			||||||
        let l = this.listenersById[sid]
 | 
					 | 
				
			||||||
        if (l == null) {
 | 
					 | 
				
			||||||
          l = []
 | 
					 | 
				
			||||||
          this.listenersById[sid] = l
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        l.push(listener)
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					      this.gc1 = this.gc1.filter(filter)
 | 
				
			||||||
      this.listenersByIdExecuteNow.push({
 | 
					      this.gc2 = this.gc2.filter(filter)
 | 
				
			||||||
        op: op
 | 
					      delete op.gc
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    destroy () {
 | 
				
			||||||
 | 
					      clearInterval(this.gcInterval)
 | 
				
			||||||
 | 
					      this.gcInterval = null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setUserId (userId) {
 | 
				
			||||||
 | 
					      var self = this
 | 
				
			||||||
 | 
					      return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					        self.requestTransaction(function * () {
 | 
				
			||||||
 | 
					          self.userId = userId
 | 
				
			||||||
 | 
					          self.opClock = (yield* this.getState(userId)).clock
 | 
				
			||||||
 | 
					          if (self.whenUserIdSetListener != null) {
 | 
				
			||||||
 | 
					            self.whenUserIdSetListener()
 | 
				
			||||||
 | 
					            self.whenUserIdSetListener = null
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          resolve()
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    whenUserIdSet (f) {
 | 
				
			||||||
    if (this.listenersByIdRequestPending) {
 | 
					      if (this.userId != null) {
 | 
				
			||||||
      return
 | 
					        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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.listenersByIdRequestPending = true
 | 
					      * get a transaction
 | 
				
			||||||
    var store = this
 | 
					      * 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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.requestTransaction(function * () {
 | 
					        for (let key in ids) {
 | 
				
			||||||
      var exeNow = store.listenersByIdExecuteNow
 | 
					          let id = ids[key]
 | 
				
			||||||
      store.listenersByIdExecuteNow = []
 | 
					          let sid = JSON.stringify(id)
 | 
				
			||||||
 | 
					          let l = this.listenersById[sid]
 | 
				
			||||||
      var ls = store.listenersById
 | 
					          if (l == null) {
 | 
				
			||||||
      store.listenersById = {}
 | 
					            l = []
 | 
				
			||||||
 | 
					            this.listenersById[sid] = l
 | 
				
			||||||
      store.listenersByIdRequestPending = false
 | 
					          }
 | 
				
			||||||
 | 
					          l.push(listener)
 | 
				
			||||||
      for (let key in exeNow) {
 | 
					        }
 | 
				
			||||||
        let o = exeNow[key].op
 | 
					      } else {
 | 
				
			||||||
        yield* store.tryExecute.call(this, o)
 | 
					        this.listenersByIdExecuteNow.push({
 | 
				
			||||||
 | 
					          op: op
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var sid in ls) {
 | 
					      if (this.listenersByIdRequestPending) {
 | 
				
			||||||
        var l = ls[sid]
 | 
					        return
 | 
				
			||||||
        var id = JSON.parse(sid)
 | 
					      }
 | 
				
			||||||
        if ((yield* this.getOperation(id)) == null) {
 | 
					
 | 
				
			||||||
          store.listenersById[sid] = l
 | 
					      this.listenersByIdRequestPending = true
 | 
				
			||||||
        } else {
 | 
					      var store = this
 | 
				
			||||||
          for (let key in l) {
 | 
					
 | 
				
			||||||
            let listener = l[key]
 | 
					      this.requestTransaction(function * () {
 | 
				
			||||||
            let o = listener.op
 | 
					        var exeNow = store.listenersByIdExecuteNow
 | 
				
			||||||
            if (--listener.missing === 0) {
 | 
					        store.listenersByIdExecuteNow = []
 | 
				
			||||||
              yield* store.tryExecute.call(this, o)
 | 
					
 | 
				
			||||||
 | 
					        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) {
 | 
					 | 
				
			||||||
    this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
 | 
					 | 
				
			||||||
    if (op.struct === 'Delete') {
 | 
					 | 
				
			||||||
      yield* Y.Struct.Delete.execute.call(this, op)
 | 
					 | 
				
			||||||
      yield* this.store.operationAdded(this, op)
 | 
					 | 
				
			||||||
    } else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
 | 
					 | 
				
			||||||
      yield* Y.Struct[op.struct].execute.call(this, op)
 | 
					 | 
				
			||||||
      yield* this.addOperation(op)
 | 
					 | 
				
			||||||
      yield* this.store.operationAdded(this, op)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    /*
 | 
				
			||||||
  // called by a transaction when an operation is added
 | 
					      Actually execute an operation, when all expected operations are available.
 | 
				
			||||||
  * operationAdded (transaction, op) {
 | 
					    */
 | 
				
			||||||
    if (op.struct === 'Delete') {
 | 
					    * tryExecute (op) {
 | 
				
			||||||
      var target = yield* transaction.getOperation(op.target)
 | 
					      this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
 | 
				
			||||||
      if (target != null) {
 | 
					      if (op.struct === 'Delete') {
 | 
				
			||||||
        var type = transaction.store.initializedTypes[JSON.stringify(target.parent)]
 | 
					        yield* Y.Struct.Delete.execute.call(this, op)
 | 
				
			||||||
        if (type != null) {
 | 
					        yield* this.store.operationAdded(this, op)
 | 
				
			||||||
          yield* type._changed(transaction, {
 | 
					      } else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
 | 
				
			||||||
            struct: 'Delete',
 | 
					        yield* Y.Struct[op.struct].execute.call(this, op)
 | 
				
			||||||
            target: op.target
 | 
					        yield* this.addOperation(op)
 | 
				
			||||||
          })
 | 
					        yield* this.store.operationAdded(this, op)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // called by a transaction when an operation is added
 | 
				
			||||||
 | 
					    * operationAdded (transaction, op) {
 | 
				
			||||||
 | 
					      if (op.struct === 'Delete') {
 | 
				
			||||||
 | 
					        var target = yield* transaction.getOperation(op.target)
 | 
				
			||||||
 | 
					        if (target != null) {
 | 
				
			||||||
 | 
					          var type = transaction.store.initializedTypes[JSON.stringify(target.parent)]
 | 
				
			||||||
 | 
					          if (type != null) {
 | 
				
			||||||
 | 
					            yield* type._changed(transaction, {
 | 
				
			||||||
 | 
					              struct: 'Delete',
 | 
				
			||||||
 | 
					              target: op.target
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      } else {
 | 
				
			||||||
    } else {
 | 
					        // increase SS
 | 
				
			||||||
      // increase SS
 | 
					        var o = op
 | 
				
			||||||
      var o = op
 | 
					        var state = yield* transaction.getState(op.id[0])
 | 
				
			||||||
      var state = yield* transaction.getState(op.id[0])
 | 
					        while (o != null && o.id[1] === state.clock && op.id[0] === o.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
 | 
				
			||||||
        // either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
 | 
					          state.clock++
 | 
				
			||||||
        state.clock++
 | 
					          yield* transaction.checkDeleteStoreForState(state)
 | 
				
			||||||
        yield* transaction.checkDeleteStoreForState(state)
 | 
					          o = yield* transaction.os.findNext(o.id)
 | 
				
			||||||
        o = yield* transaction.os.findNext(o.id)
 | 
					        }
 | 
				
			||||||
      }
 | 
					        yield* transaction.setState(state)
 | 
				
			||||||
      yield* transaction.setState(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // notify whenOperation listeners (by id)
 | 
					        // notify whenOperation listeners (by id)
 | 
				
			||||||
      var sid = JSON.stringify(op.id)
 | 
					        var sid = JSON.stringify(op.id)
 | 
				
			||||||
      var l = this.listenersById[sid]
 | 
					        var l = this.listenersById[sid]
 | 
				
			||||||
      delete this.listenersById[sid]
 | 
					        delete this.listenersById[sid]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (l != null) {
 | 
					        if (l != null) {
 | 
				
			||||||
        for (var key in l) {
 | 
					          for (var key in l) {
 | 
				
			||||||
          var listener = l[key]
 | 
					            var listener = l[key]
 | 
				
			||||||
          if (--listener.missing === 0) {
 | 
					            if (--listener.missing === 0) {
 | 
				
			||||||
            this.whenOperationsExist([], listener.op)
 | 
					              this.whenOperationsExist([], listener.op)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var t = this.initializedTypes[JSON.stringify(op.parent)]
 | 
				
			||||||
 | 
					        // notify parent, if it has been initialized as a custom type
 | 
				
			||||||
 | 
					        if (t != null) {
 | 
				
			||||||
 | 
					          yield* t._changed(transaction, Y.utils.copyObject(op))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Delete if DS says this is actually deleted
 | 
				
			||||||
 | 
					        if (!op.deleted && (yield* transaction.isDeleted(op.id))) {
 | 
				
			||||||
 | 
					          var delop = {
 | 
				
			||||||
 | 
					            struct: 'Delete',
 | 
				
			||||||
 | 
					            target: op.id
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          yield* Y.Struct['Delete'].execute.call(transaction, delop)
 | 
				
			||||||
 | 
					          if (t != null) {
 | 
				
			||||||
 | 
					            yield* t._changed(transaction, delop)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var t = this.initializedTypes[JSON.stringify(op.parent)]
 | 
					    }
 | 
				
			||||||
      // notify parent, if it has been initialized as a custom type
 | 
					    getNextRequest () {
 | 
				
			||||||
      if (t != null) {
 | 
					      if (this.waitingTransactions.length === 0) {
 | 
				
			||||||
        yield* t._changed(transaction, Y.utils.copyObject(op))
 | 
					        this.transactionInProgress = false
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return this.waitingTransactions.shift()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
      // Delete if DS says this is actually deleted
 | 
					    requestTransaction (makeGen, callImmediately) {
 | 
				
			||||||
      if (!op.deleted && (yield* transaction.isDeleted(op.id))) {
 | 
					      if (callImmediately) {
 | 
				
			||||||
        var delop = {
 | 
					        this.transact(makeGen)
 | 
				
			||||||
          struct: 'Delete',
 | 
					      } else if (!this.transactionInProgress) {
 | 
				
			||||||
          target: op.id
 | 
					        this.transactionInProgress = true
 | 
				
			||||||
        }
 | 
					        var self = this
 | 
				
			||||||
        yield* Y.Struct['Delete'].execute.call(transaction, delop)
 | 
					        setTimeout(function () {
 | 
				
			||||||
        if (t != null) {
 | 
					          self.transact(makeGen)
 | 
				
			||||||
          yield* t._changed(transaction, delop)
 | 
					        }, 0)
 | 
				
			||||||
        }
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.waitingTransactions.push(makeGen)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  getNextRequest () {
 | 
					  Y.AbstractDatabase = AbstractDatabase
 | 
				
			||||||
    if (this.waitingTransactions.length === 0) {
 | 
					 | 
				
			||||||
      this.transactionInProgress = false
 | 
					 | 
				
			||||||
      return null
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return this.waitingTransactions.shift()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  requestTransaction (makeGen, callImmediately) {
 | 
					 | 
				
			||||||
    if (callImmediately) {
 | 
					 | 
				
			||||||
      this.transact(makeGen)
 | 
					 | 
				
			||||||
    } else if (!this.transactionInProgress) {
 | 
					 | 
				
			||||||
      this.transactionInProgress = true
 | 
					 | 
				
			||||||
      var self = this
 | 
					 | 
				
			||||||
      setTimeout(function () {
 | 
					 | 
				
			||||||
        self.transact(makeGen)
 | 
					 | 
				
			||||||
      }, 0)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.waitingTransactions.push(makeGen)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
Y.AbstractDatabase = AbstractDatabase
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
/* global Y, async, databases */
 | 
					/* global async, databases */
 | 
				
			||||||
/* eslint-env browser,jasmine,console */
 | 
					/* eslint-env browser,jasmine,console */
 | 
				
			||||||
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Y = require('./SpecHelper.js')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for (let database of databases) {
 | 
					for (let database of databases) {
 | 
				
			||||||
  describe(`Database (${database})`, function () {
 | 
					  describe(`Database (${database})`, function () {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,6 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.IndexedDB = (function () {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  class Store {
 | 
					  class Store {
 | 
				
			||||||
    constructor (transaction, name) {
 | 
					    constructor (transaction, name) {
 | 
				
			||||||
      this.store = transaction.objectStore(name)
 | 
					      this.store = transaction.objectStore(name)
 | 
				
			||||||
@ -177,5 +175,5 @@ Y.IndexedDB = (function () {
 | 
				
			|||||||
      yield window.indexedDB.deleteDatabase(this.namespace)
 | 
					      yield window.indexedDB.deleteDatabase(this.namespace)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return OperationStore
 | 
					  Y.IndexedDB = OperationStore
 | 
				
			||||||
})()
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
/* eslint-env browser,jasmine */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (typeof window !== 'undefined' && false) {
 | 
					 | 
				
			||||||
  describe('IndexedDB', function () {
 | 
					 | 
				
			||||||
    var ob
 | 
					 | 
				
			||||||
    beforeAll(function () {
 | 
					 | 
				
			||||||
      ob = new Y.IndexedDB(null, {namespace: 'Test', gcTimeout: -1})
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    afterAll(function (done) {
 | 
					 | 
				
			||||||
      ob.requestTransaction(function *() {
 | 
					 | 
				
			||||||
        yield* ob.removeDatabase()
 | 
					 | 
				
			||||||
        ob = null
 | 
					 | 
				
			||||||
        done()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.Memory = (function () {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  class Transaction extends Y.Transaction {
 | 
					  class Transaction extends Y.Transaction {
 | 
				
			||||||
    constructor (store) {
 | 
					    constructor (store) {
 | 
				
			||||||
      super(store)
 | 
					      super(store)
 | 
				
			||||||
@ -59,5 +58,5 @@ Y.Memory = (function () {
 | 
				
			|||||||
      delete this.ds
 | 
					      delete this.ds
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return Database
 | 
					  Y.Memory = Database
 | 
				
			||||||
})()
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,489 +1,490 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
  This file contains a not so fancy implemantion of a Red Black Tree.
 | 
					  This file contains a not so fancy implemantion of a Red Black Tree.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class N {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  // A created node is always red!
 | 
					  class N {
 | 
				
			||||||
  constructor (val) {
 | 
					    // A created node is always red!
 | 
				
			||||||
    this.val = val
 | 
					    constructor (val) {
 | 
				
			||||||
    this.color = true
 | 
					      this.val = val
 | 
				
			||||||
    this._left = null
 | 
					      this.color = true
 | 
				
			||||||
    this._right = null
 | 
					      this._left = null
 | 
				
			||||||
    this._parent = null
 | 
					      this._right = null
 | 
				
			||||||
    if (val.id === null) {
 | 
					      this._parent = null
 | 
				
			||||||
      throw new Error('You must define id!')
 | 
					      if (val.id === null) {
 | 
				
			||||||
    }
 | 
					        throw new Error('You must define id!')
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  isRed () { return this.color }
 | 
					 | 
				
			||||||
  isBlack () { return !this.color }
 | 
					 | 
				
			||||||
  redden () { this.color = true; return this }
 | 
					 | 
				
			||||||
  blacken () { this.color = false; return this }
 | 
					 | 
				
			||||||
  get grandparent () {
 | 
					 | 
				
			||||||
    return this.parent.parent
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  get parent () {
 | 
					 | 
				
			||||||
    return this._parent
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  get sibling () {
 | 
					 | 
				
			||||||
    return (this === this.parent.left)
 | 
					 | 
				
			||||||
      ? this.parent.right : this.parent.left
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  get left () {
 | 
					 | 
				
			||||||
    return this._left
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  get right () {
 | 
					 | 
				
			||||||
    return this._right
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  set left (n) {
 | 
					 | 
				
			||||||
    if (n !== null) {
 | 
					 | 
				
			||||||
      n._parent = this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this._left = n
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  set right (n) {
 | 
					 | 
				
			||||||
    if (n !== null) {
 | 
					 | 
				
			||||||
      n._parent = this
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this._right = n
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  rotateLeft (tree) {
 | 
					 | 
				
			||||||
    var parent = this.parent
 | 
					 | 
				
			||||||
    var newParent = this.right
 | 
					 | 
				
			||||||
    var newRight = this.right.left
 | 
					 | 
				
			||||||
    newParent.left = this
 | 
					 | 
				
			||||||
    this.right = newRight
 | 
					 | 
				
			||||||
    if (parent === null) {
 | 
					 | 
				
			||||||
      tree.root = newParent
 | 
					 | 
				
			||||||
      newParent._parent = null
 | 
					 | 
				
			||||||
    } else if (parent.left === this) {
 | 
					 | 
				
			||||||
      parent.left = newParent
 | 
					 | 
				
			||||||
    } else if (parent.right === this) {
 | 
					 | 
				
			||||||
      parent.right = newParent
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw new Error('The elements are wrongly connected!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  next () {
 | 
					 | 
				
			||||||
    if (this.right !== null) {
 | 
					 | 
				
			||||||
      // search the most left node in the right tree
 | 
					 | 
				
			||||||
      var o = this.right
 | 
					 | 
				
			||||||
      while (o.left !== null) {
 | 
					 | 
				
			||||||
        o = o.left
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return o
 | 
					    }
 | 
				
			||||||
    } else {
 | 
					    isRed () { return this.color }
 | 
				
			||||||
      var p = this
 | 
					    isBlack () { return !this.color }
 | 
				
			||||||
      while (p.parent !== null && p !== p.parent.left) {
 | 
					    redden () { this.color = true; return this }
 | 
				
			||||||
        p = p.parent
 | 
					    blacken () { this.color = false; return this }
 | 
				
			||||||
 | 
					    get grandparent () {
 | 
				
			||||||
 | 
					      return this.parent.parent
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get parent () {
 | 
				
			||||||
 | 
					      return this._parent
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get sibling () {
 | 
				
			||||||
 | 
					      return (this === this.parent.left)
 | 
				
			||||||
 | 
					        ? this.parent.right : this.parent.left
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get left () {
 | 
				
			||||||
 | 
					      return this._left
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    get right () {
 | 
				
			||||||
 | 
					      return this._right
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    set left (n) {
 | 
				
			||||||
 | 
					      if (n !== null) {
 | 
				
			||||||
 | 
					        n._parent = this
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return p.parent
 | 
					      this._left = n
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    set right (n) {
 | 
				
			||||||
  prev () {
 | 
					      if (n !== null) {
 | 
				
			||||||
    if (this.left !== null) {
 | 
					        n._parent = this
 | 
				
			||||||
      // search the most right node in the left tree
 | 
					 | 
				
			||||||
      var o = this.left
 | 
					 | 
				
			||||||
      while (o.right !== null) {
 | 
					 | 
				
			||||||
        o = o.right
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return o
 | 
					      this._right = n
 | 
				
			||||||
    } else {
 | 
					    }
 | 
				
			||||||
      var p = this
 | 
					    rotateLeft (tree) {
 | 
				
			||||||
      while (p.parent !== null && p !== p.parent.right) {
 | 
					      var parent = this.parent
 | 
				
			||||||
        p = p.parent
 | 
					      var newParent = this.right
 | 
				
			||||||
 | 
					      var newRight = this.right.left
 | 
				
			||||||
 | 
					      newParent.left = this
 | 
				
			||||||
 | 
					      this.right = newRight
 | 
				
			||||||
 | 
					      if (parent === null) {
 | 
				
			||||||
 | 
					        tree.root = newParent
 | 
				
			||||||
 | 
					        newParent._parent = null
 | 
				
			||||||
 | 
					      } else if (parent.left === this) {
 | 
				
			||||||
 | 
					        parent.left = newParent
 | 
				
			||||||
 | 
					      } else if (parent.right === this) {
 | 
				
			||||||
 | 
					        parent.right = newParent
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw new Error('The elements are wrongly connected!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return p.parent
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    next () {
 | 
				
			||||||
  rotateRight (tree) {
 | 
					      if (this.right !== null) {
 | 
				
			||||||
    var parent = this.parent
 | 
					        // search the most left node in the right tree
 | 
				
			||||||
    var newParent = this.left
 | 
					        var o = this.right
 | 
				
			||||||
    var newLeft = this.left.right
 | 
					        while (o.left !== null) {
 | 
				
			||||||
    newParent.right = this
 | 
					 | 
				
			||||||
    this.left = newLeft
 | 
					 | 
				
			||||||
    if (parent === null) {
 | 
					 | 
				
			||||||
      tree.root = newParent
 | 
					 | 
				
			||||||
      newParent._parent = null
 | 
					 | 
				
			||||||
    } else if (parent.left === this) {
 | 
					 | 
				
			||||||
      parent.left = newParent
 | 
					 | 
				
			||||||
    } else if (parent.right === this) {
 | 
					 | 
				
			||||||
      parent.right = newParent
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      throw new Error('The elements are wrongly connected!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  getUncle () {
 | 
					 | 
				
			||||||
    // we can assume that grandparent exists when this is called!
 | 
					 | 
				
			||||||
    if (this.parent === this.parent.parent.left) {
 | 
					 | 
				
			||||||
      return this.parent.parent.right
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return this.parent.parent.left
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RBTree {
 | 
					 | 
				
			||||||
  constructor () {
 | 
					 | 
				
			||||||
    this.root = null
 | 
					 | 
				
			||||||
    this.length = 0
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  * findNext (id) {
 | 
					 | 
				
			||||||
    return yield* this.findWithLowerBound([id[0], id[1] + 1])
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  * findPrev (id) {
 | 
					 | 
				
			||||||
    return yield* this.findWithUpperBound([id[0], id[1] - 1])
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  findNodeWithLowerBound (from) {
 | 
					 | 
				
			||||||
    if (from === void 0) {
 | 
					 | 
				
			||||||
      throw new Error('You must define from!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var o = this.root
 | 
					 | 
				
			||||||
    if (o === null) {
 | 
					 | 
				
			||||||
      return null
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      while (true) {
 | 
					 | 
				
			||||||
        if ((from === null || Y.utils.smaller(from, o.val.id)) && o.left !== null) {
 | 
					 | 
				
			||||||
          // o is included in the bound
 | 
					 | 
				
			||||||
          // try to find an element that is closer to the bound
 | 
					 | 
				
			||||||
          o = o.left
 | 
					          o = o.left
 | 
				
			||||||
        } else if (from !== null && Y.utils.smaller(o.val.id, from)) {
 | 
					        }
 | 
				
			||||||
          // o is not within the bound, maybe one of the right elements is..
 | 
					        return o
 | 
				
			||||||
          if (o.right !== null) {
 | 
					      } else {
 | 
				
			||||||
 | 
					        var p = this
 | 
				
			||||||
 | 
					        while (p.parent !== null && p !== p.parent.left) {
 | 
				
			||||||
 | 
					          p = p.parent
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return p.parent
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    prev () {
 | 
				
			||||||
 | 
					      if (this.left !== null) {
 | 
				
			||||||
 | 
					        // search the most right node in the left tree
 | 
				
			||||||
 | 
					        var o = this.left
 | 
				
			||||||
 | 
					        while (o.right !== null) {
 | 
				
			||||||
 | 
					          o = o.right
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return o
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        var p = this
 | 
				
			||||||
 | 
					        while (p.parent !== null && p !== p.parent.right) {
 | 
				
			||||||
 | 
					          p = p.parent
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return p.parent
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    rotateRight (tree) {
 | 
				
			||||||
 | 
					      var parent = this.parent
 | 
				
			||||||
 | 
					      var newParent = this.left
 | 
				
			||||||
 | 
					      var newLeft = this.left.right
 | 
				
			||||||
 | 
					      newParent.right = this
 | 
				
			||||||
 | 
					      this.left = newLeft
 | 
				
			||||||
 | 
					      if (parent === null) {
 | 
				
			||||||
 | 
					        tree.root = newParent
 | 
				
			||||||
 | 
					        newParent._parent = null
 | 
				
			||||||
 | 
					      } else if (parent.left === this) {
 | 
				
			||||||
 | 
					        parent.left = newParent
 | 
				
			||||||
 | 
					      } else if (parent.right === this) {
 | 
				
			||||||
 | 
					        parent.right = newParent
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw new Error('The elements are wrongly connected!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    getUncle () {
 | 
				
			||||||
 | 
					      // we can assume that grandparent exists when this is called!
 | 
				
			||||||
 | 
					      if (this.parent === this.parent.parent.left) {
 | 
				
			||||||
 | 
					        return this.parent.parent.right
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return this.parent.parent.left
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class RBTree {
 | 
				
			||||||
 | 
					    constructor () {
 | 
				
			||||||
 | 
					      this.root = null
 | 
				
			||||||
 | 
					      this.length = 0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * findNext (id) {
 | 
				
			||||||
 | 
					      return yield* this.findWithLowerBound([id[0], id[1] + 1])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * findPrev (id) {
 | 
				
			||||||
 | 
					      return yield* this.findWithUpperBound([id[0], id[1] - 1])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    findNodeWithLowerBound (from) {
 | 
				
			||||||
 | 
					      if (from === void 0) {
 | 
				
			||||||
 | 
					        throw new Error('You must define from!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var o = this.root
 | 
				
			||||||
 | 
					      if (o === null) {
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					          if ((from === null || Y.utils.smaller(from, o.val.id)) && o.left !== null) {
 | 
				
			||||||
 | 
					            // o is included in the bound
 | 
				
			||||||
 | 
					            // try to find an element that is closer to the bound
 | 
				
			||||||
 | 
					            o = o.left
 | 
				
			||||||
 | 
					          } else if (from !== null && Y.utils.smaller(o.val.id, from)) {
 | 
				
			||||||
 | 
					            // o is not within the bound, maybe one of the right elements is..
 | 
				
			||||||
 | 
					            if (o.right !== null) {
 | 
				
			||||||
 | 
					              o = o.right
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              // there is no right element. Search for the next bigger element,
 | 
				
			||||||
 | 
					              // this should be within the bounds
 | 
				
			||||||
 | 
					              return o.next()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return o
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    findNodeWithUpperBound (to) {
 | 
				
			||||||
 | 
					      if (to === void 0) {
 | 
				
			||||||
 | 
					        throw new Error('You must define from!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var o = this.root
 | 
				
			||||||
 | 
					      if (o === null) {
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					          if ((to === null || Y.utils.smaller(o.val.id, to)) && o.right !== null) {
 | 
				
			||||||
 | 
					            // o is included in the bound
 | 
				
			||||||
 | 
					            // try to find an element that is closer to the bound
 | 
				
			||||||
 | 
					            o = o.right
 | 
				
			||||||
 | 
					          } else if (to !== null && Y.utils.smaller(to, o.val.id)) {
 | 
				
			||||||
 | 
					            // o is not within the bound, maybe one of the left elements is..
 | 
				
			||||||
 | 
					            if (o.left !== null) {
 | 
				
			||||||
 | 
					              o = o.left
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              // there is no left element. Search for the prev smaller element,
 | 
				
			||||||
 | 
					              // this should be within the bounds
 | 
				
			||||||
 | 
					              return o.prev()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return o
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * findWithLowerBound (from) {
 | 
				
			||||||
 | 
					      var n = this.findNodeWithLowerBound(from)
 | 
				
			||||||
 | 
					      return n == null ? null : n.val
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * findWithUpperBound (to) {
 | 
				
			||||||
 | 
					      var n = this.findNodeWithUpperBound(to)
 | 
				
			||||||
 | 
					      return n == null ? null : n.val
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * iterate (t, from, to, f) {
 | 
				
			||||||
 | 
					      var o = this.findNodeWithLowerBound(from)
 | 
				
			||||||
 | 
					      while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
 | 
				
			||||||
 | 
					        yield* f.call(t, o.val)
 | 
				
			||||||
 | 
					        o = o.next()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * logTable (from, to, filter) {
 | 
				
			||||||
 | 
					      if (filter == null) {
 | 
				
			||||||
 | 
					        filter = function () {
 | 
				
			||||||
 | 
					          return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (from == null) { from = null }
 | 
				
			||||||
 | 
					      if (to == null) { to = null }
 | 
				
			||||||
 | 
					      var os = []
 | 
				
			||||||
 | 
					      yield* this.iterate(this, from, to, function * (o) {
 | 
				
			||||||
 | 
					        if (filter(o)) {
 | 
				
			||||||
 | 
					          var o_ = {}
 | 
				
			||||||
 | 
					          for (var key in o) {
 | 
				
			||||||
 | 
					            if (typeof o[key] === 'object') {
 | 
				
			||||||
 | 
					              o_[key] = JSON.stringify(o[key])
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              o_[key] = o[key]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          os.push(o_)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      if (console.table != null) {
 | 
				
			||||||
 | 
					        console.table(os)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * find (id) {
 | 
				
			||||||
 | 
					      var n
 | 
				
			||||||
 | 
					      return (n = this.findNode(id)) ? n.val : null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    findNode (id) {
 | 
				
			||||||
 | 
					      if (id == null || id.constructor !== Array) {
 | 
				
			||||||
 | 
					        throw new Error('Expect id to be an array!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var o = this.root
 | 
				
			||||||
 | 
					      if (o === null) {
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					          if (o === null) {
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (Y.utils.smaller(id, o.val.id)) {
 | 
				
			||||||
 | 
					            o = o.left
 | 
				
			||||||
 | 
					          } else if (Y.utils.smaller(o.val.id, id)) {
 | 
				
			||||||
            o = o.right
 | 
					            o = o.right
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            // there is no right element. Search for the next bigger element,
 | 
					            return o
 | 
				
			||||||
            // this should be within the bounds
 | 
					 | 
				
			||||||
            return o.next()
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return o
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    * delete (id) {
 | 
				
			||||||
  findNodeWithUpperBound (to) {
 | 
					      if (id == null || id.constructor !== Array) {
 | 
				
			||||||
    if (to === void 0) {
 | 
					        throw new Error('id is expected to be an Array!')
 | 
				
			||||||
      throw new Error('You must define from!')
 | 
					      }
 | 
				
			||||||
    }
 | 
					      var d = this.findNode(id)
 | 
				
			||||||
    var o = this.root
 | 
					      if (d == null) {
 | 
				
			||||||
    if (o === null) {
 | 
					        throw new Error('Element does not exist!')
 | 
				
			||||||
      return null
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					      this.length--
 | 
				
			||||||
      while (true) {
 | 
					      if (d.left !== null && d.right !== null) {
 | 
				
			||||||
        if ((to === null || Y.utils.smaller(o.val.id, to)) && o.right !== null) {
 | 
					        // switch d with the greates element in the left subtree.
 | 
				
			||||||
          // o is included in the bound
 | 
					        // o should have at most one child.
 | 
				
			||||||
          // try to find an element that is closer to the bound
 | 
					        var o = d.left
 | 
				
			||||||
 | 
					        // find
 | 
				
			||||||
 | 
					        while (o.right !== null) {
 | 
				
			||||||
          o = o.right
 | 
					          o = o.right
 | 
				
			||||||
        } else if (to !== null && Y.utils.smaller(to, o.val.id)) {
 | 
					 | 
				
			||||||
          // o is not within the bound, maybe one of the left elements is..
 | 
					 | 
				
			||||||
          if (o.left !== null) {
 | 
					 | 
				
			||||||
            o = o.left
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            // there is no left element. Search for the prev smaller element,
 | 
					 | 
				
			||||||
            // this should be within the bounds
 | 
					 | 
				
			||||||
            return o.prev()
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return o
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // switch
 | 
				
			||||||
 | 
					        d.val = o.val
 | 
				
			||||||
 | 
					        d = o
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					      // d has at most one child
 | 
				
			||||||
  }
 | 
					      // let n be the node that replaces d
 | 
				
			||||||
  * findWithLowerBound (from) {
 | 
					      var isFakeChild
 | 
				
			||||||
    var n = this.findNodeWithLowerBound(from)
 | 
					      var child = d.left || d.right
 | 
				
			||||||
    return n == null ? null : n.val
 | 
					      if (child === null) {
 | 
				
			||||||
  }
 | 
					        isFakeChild = true
 | 
				
			||||||
  * findWithUpperBound (to) {
 | 
					        child = new N({id: 0})
 | 
				
			||||||
    var n = this.findNodeWithUpperBound(to)
 | 
					        child.blacken()
 | 
				
			||||||
    return n == null ? null : n.val
 | 
					        d.right = child
 | 
				
			||||||
  }
 | 
					      } else {
 | 
				
			||||||
  * iterate (t, from, to, f) {
 | 
					        isFakeChild = false
 | 
				
			||||||
    var o = this.findNodeWithLowerBound(from)
 | 
					 | 
				
			||||||
    while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
 | 
					 | 
				
			||||||
      yield* f.call(t, o.val)
 | 
					 | 
				
			||||||
      o = o.next()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  * logTable (from, to, filter) {
 | 
					 | 
				
			||||||
    if (filter == null) {
 | 
					 | 
				
			||||||
      filter = function () {
 | 
					 | 
				
			||||||
        return true
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (from == null) { from = null }
 | 
					 | 
				
			||||||
    if (to == null) { to = null }
 | 
					 | 
				
			||||||
    var os = []
 | 
					 | 
				
			||||||
    yield* this.iterate(this, from, to, function * (o) {
 | 
					 | 
				
			||||||
      if (filter(o)) {
 | 
					 | 
				
			||||||
        var o_ = {}
 | 
					 | 
				
			||||||
        for (var key in o) {
 | 
					 | 
				
			||||||
          if (typeof o[key] === 'object') {
 | 
					 | 
				
			||||||
            o_[key] = JSON.stringify(o[key])
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            o_[key] = o[key]
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        os.push(o_)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    if (console.table != null) {
 | 
					 | 
				
			||||||
      console.table(os)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  * find (id) {
 | 
					 | 
				
			||||||
    var n
 | 
					 | 
				
			||||||
    return (n = this.findNode(id)) ? n.val : null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  findNode (id) {
 | 
					 | 
				
			||||||
    if (id == null || id.constructor !== Array) {
 | 
					 | 
				
			||||||
      throw new Error('Expect id to be an array!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var o = this.root
 | 
					 | 
				
			||||||
    if (o === null) {
 | 
					 | 
				
			||||||
      return false
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      while (true) {
 | 
					 | 
				
			||||||
        if (o === null) {
 | 
					 | 
				
			||||||
          return false
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (Y.utils.smaller(id, o.val.id)) {
 | 
					 | 
				
			||||||
          o = o.left
 | 
					 | 
				
			||||||
        } else if (Y.utils.smaller(o.val.id, id)) {
 | 
					 | 
				
			||||||
          o = o.right
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          return o
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  * delete (id) {
 | 
					 | 
				
			||||||
    if (id == null || id.constructor !== Array) {
 | 
					 | 
				
			||||||
      throw new Error('id is expected to be an Array!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var d = this.findNode(id)
 | 
					 | 
				
			||||||
    if (d == null) {
 | 
					 | 
				
			||||||
      throw new Error('Element does not exist!')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.length--
 | 
					 | 
				
			||||||
    if (d.left !== null && d.right !== null) {
 | 
					 | 
				
			||||||
      // switch d with the greates element in the left subtree.
 | 
					 | 
				
			||||||
      // o should have at most one child.
 | 
					 | 
				
			||||||
      var o = d.left
 | 
					 | 
				
			||||||
      // find
 | 
					 | 
				
			||||||
      while (o.right !== null) {
 | 
					 | 
				
			||||||
        o = o.right
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // switch
 | 
					 | 
				
			||||||
      d.val = o.val
 | 
					 | 
				
			||||||
      d = o
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // d has at most one child
 | 
					 | 
				
			||||||
    // let n be the node that replaces d
 | 
					 | 
				
			||||||
    var isFakeChild
 | 
					 | 
				
			||||||
    var child = d.left || d.right
 | 
					 | 
				
			||||||
    if (child === null) {
 | 
					 | 
				
			||||||
      isFakeChild = true
 | 
					 | 
				
			||||||
      child = new N({id: 0})
 | 
					 | 
				
			||||||
      child.blacken()
 | 
					 | 
				
			||||||
      d.right = child
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      isFakeChild = false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (d.parent === null) {
 | 
					      if (d.parent === null) {
 | 
				
			||||||
      if (!isFakeChild) {
 | 
					        if (!isFakeChild) {
 | 
				
			||||||
        this.root = child
 | 
					          this.root = child
 | 
				
			||||||
        child.blacken()
 | 
					          child.blacken()
 | 
				
			||||||
        child._parent = null
 | 
					          child._parent = null
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          this.root = null
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      } else if (d.parent.left === d) {
 | 
				
			||||||
 | 
					        d.parent.left = child
 | 
				
			||||||
 | 
					      } else if (d.parent.right === d) {
 | 
				
			||||||
 | 
					        d.parent.right = child
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.root = null
 | 
					        throw new Error('Impossible!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return
 | 
					      if (d.isBlack()) {
 | 
				
			||||||
    } else if (d.parent.left === d) {
 | 
					        if (child.isRed()) {
 | 
				
			||||||
      d.parent.left = child
 | 
					          child.blacken()
 | 
				
			||||||
    } else if (d.parent.right === d) {
 | 
					        } else {
 | 
				
			||||||
      d.parent.right = child
 | 
					          this._fixDelete(child)
 | 
				
			||||||
    } else {
 | 
					        }
 | 
				
			||||||
      throw new Error('Impossible!')
 | 
					      }
 | 
				
			||||||
    }
 | 
					      this.root.blacken()
 | 
				
			||||||
    if (d.isBlack()) {
 | 
					      if (isFakeChild) {
 | 
				
			||||||
      if (child.isRed()) {
 | 
					        if (child.parent.left === child) {
 | 
				
			||||||
        child.blacken()
 | 
					          child.parent.left = null
 | 
				
			||||||
      } else {
 | 
					        } else if (child.parent.right === child) {
 | 
				
			||||||
        this._fixDelete(child)
 | 
					          child.parent.right = null
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw new Error('Impossible #3')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.root.blacken()
 | 
					    _fixDelete (n) {
 | 
				
			||||||
    if (isFakeChild) {
 | 
					      function isBlack (node) {
 | 
				
			||||||
      if (child.parent.left === child) {
 | 
					        return node !== null ? node.isBlack() : true
 | 
				
			||||||
        child.parent.left = null
 | 
					 | 
				
			||||||
      } else if (child.parent.right === child) {
 | 
					 | 
				
			||||||
        child.parent.right = null
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new Error('Impossible #3')
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					      function isRed (node) {
 | 
				
			||||||
  }
 | 
					        return node !== null ? node.isRed() : false
 | 
				
			||||||
  _fixDelete (n) {
 | 
					 | 
				
			||||||
    function isBlack (node) {
 | 
					 | 
				
			||||||
      return node !== null ? node.isBlack() : true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function isRed (node) {
 | 
					 | 
				
			||||||
      return node !== null ? node.isRed() : false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (n.parent === null) {
 | 
					 | 
				
			||||||
      // this can only be called after the first iteration of fixDelete.
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // d was already replaced by the child
 | 
					 | 
				
			||||||
    // d is not the root
 | 
					 | 
				
			||||||
    // d and child are black
 | 
					 | 
				
			||||||
    var sibling = n.sibling
 | 
					 | 
				
			||||||
    if (isRed(sibling)) {
 | 
					 | 
				
			||||||
      // make sibling the grandfather
 | 
					 | 
				
			||||||
      n.parent.redden()
 | 
					 | 
				
			||||||
      sibling.blacken()
 | 
					 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					 | 
				
			||||||
        n.parent.rotateLeft(this)
 | 
					 | 
				
			||||||
      } else if (n === n.parent.right) {
 | 
					 | 
				
			||||||
        n.parent.rotateRight(this)
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        throw new Error('Impossible #2')
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      sibling = n.sibling
 | 
					      if (n.parent === null) {
 | 
				
			||||||
    }
 | 
					        // this can only be called after the first iteration of fixDelete.
 | 
				
			||||||
    // parent, sibling, and children of n are black
 | 
					        return
 | 
				
			||||||
    if (n.parent.isBlack() &&
 | 
					      }
 | 
				
			||||||
      sibling.isBlack() &&
 | 
					      // d was already replaced by the child
 | 
				
			||||||
      isBlack(sibling.left) &&
 | 
					      // d is not the root
 | 
				
			||||||
      isBlack(sibling.right)
 | 
					      // d and child are black
 | 
				
			||||||
    ) {
 | 
					      var sibling = n.sibling
 | 
				
			||||||
      sibling.redden()
 | 
					      if (isRed(sibling)) {
 | 
				
			||||||
      this._fixDelete(n.parent)
 | 
					        // make sibling the grandfather
 | 
				
			||||||
    } else if (n.parent.isRed() &&
 | 
					        n.parent.redden()
 | 
				
			||||||
      sibling.isBlack() &&
 | 
					        sibling.blacken()
 | 
				
			||||||
      isBlack(sibling.left) &&
 | 
					        if (n === n.parent.left) {
 | 
				
			||||||
      isBlack(sibling.right)
 | 
					          n.parent.rotateLeft(this)
 | 
				
			||||||
    ) {
 | 
					        } else if (n === n.parent.right) {
 | 
				
			||||||
      sibling.redden()
 | 
					          n.parent.rotateRight(this)
 | 
				
			||||||
      n.parent.blacken()
 | 
					        } else {
 | 
				
			||||||
    } else {
 | 
					          throw new Error('Impossible #2')
 | 
				
			||||||
      if (n === n.parent.left &&
 | 
					        }
 | 
				
			||||||
 | 
					        sibling = n.sibling
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // parent, sibling, and children of n are black
 | 
				
			||||||
 | 
					      if (n.parent.isBlack() &&
 | 
				
			||||||
        sibling.isBlack() &&
 | 
					        sibling.isBlack() &&
 | 
				
			||||||
        isRed(sibling.left) &&
 | 
					        isBlack(sibling.left) &&
 | 
				
			||||||
        isBlack(sibling.right)
 | 
					        isBlack(sibling.right)
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        sibling.redden()
 | 
					        sibling.redden()
 | 
				
			||||||
        sibling.left.blacken()
 | 
					        this._fixDelete(n.parent)
 | 
				
			||||||
        sibling.rotateRight(this)
 | 
					      } else if (n.parent.isRed() &&
 | 
				
			||||||
        sibling = n.sibling
 | 
					 | 
				
			||||||
      } else if (n === n.parent.right &&
 | 
					 | 
				
			||||||
        sibling.isBlack() &&
 | 
					        sibling.isBlack() &&
 | 
				
			||||||
        isRed(sibling.right) &&
 | 
					        isBlack(sibling.left) &&
 | 
				
			||||||
        isBlack(sibling.left)
 | 
					        isBlack(sibling.right)
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        sibling.redden()
 | 
					        sibling.redden()
 | 
				
			||||||
        sibling.right.blacken()
 | 
					        n.parent.blacken()
 | 
				
			||||||
        sibling.rotateLeft(this)
 | 
					 | 
				
			||||||
        sibling = n.sibling
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      sibling.color = n.parent.color
 | 
					 | 
				
			||||||
      n.parent.blacken()
 | 
					 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					 | 
				
			||||||
        sibling.right.blacken()
 | 
					 | 
				
			||||||
        n.parent.rotateLeft(this)
 | 
					 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        sibling.left.blacken()
 | 
					        if (n === n.parent.left &&
 | 
				
			||||||
        n.parent.rotateRight(this)
 | 
					          sibling.isBlack() &&
 | 
				
			||||||
      }
 | 
					          isRed(sibling.left) &&
 | 
				
			||||||
    }
 | 
					          isBlack(sibling.right)
 | 
				
			||||||
  }
 | 
					        ) {
 | 
				
			||||||
  * put (v) {
 | 
					          sibling.redden()
 | 
				
			||||||
    if (v == null || v.id == null || v.id.constructor !== Array) {
 | 
					          sibling.left.blacken()
 | 
				
			||||||
      throw new Error('v is expected to have an id property which is an Array!')
 | 
					          sibling.rotateRight(this)
 | 
				
			||||||
    }
 | 
					          sibling = n.sibling
 | 
				
			||||||
    var node = new N(v)
 | 
					        } else if (n === n.parent.right &&
 | 
				
			||||||
    if (this.root !== null) {
 | 
					          sibling.isBlack() &&
 | 
				
			||||||
      var p = this.root // p abbrev. parent
 | 
					          isRed(sibling.right) &&
 | 
				
			||||||
      while (true) {
 | 
					          isBlack(sibling.left)
 | 
				
			||||||
        if (Y.utils.smaller(node.val.id, p.val.id)) {
 | 
					        ) {
 | 
				
			||||||
          if (p.left === null) {
 | 
					          sibling.redden()
 | 
				
			||||||
            p.left = node
 | 
					          sibling.right.blacken()
 | 
				
			||||||
            break
 | 
					          sibling.rotateLeft(this)
 | 
				
			||||||
          } else {
 | 
					          sibling = n.sibling
 | 
				
			||||||
            p = p.left
 | 
					        }
 | 
				
			||||||
          }
 | 
					        sibling.color = n.parent.color
 | 
				
			||||||
        } else if (Y.utils.smaller(p.val.id, node.val.id)) {
 | 
					        n.parent.blacken()
 | 
				
			||||||
          if (p.right === null) {
 | 
					        if (n === n.parent.left) {
 | 
				
			||||||
            p.right = node
 | 
					          sibling.right.blacken()
 | 
				
			||||||
            break
 | 
					          n.parent.rotateLeft(this)
 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            p = p.right
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          p.val = node.val
 | 
					          sibling.left.blacken()
 | 
				
			||||||
          return p
 | 
					          n.parent.rotateRight(this)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this._fixInsert(node)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.root = node
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.length++
 | 
					    * put (v) {
 | 
				
			||||||
    this.root.blacken()
 | 
					      if (v == null || v.id == null || v.id.constructor !== Array) {
 | 
				
			||||||
    return node
 | 
					        throw new Error('v is expected to have an id property which is an Array!')
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  _fixInsert (n) {
 | 
					 | 
				
			||||||
    if (n.parent === null) {
 | 
					 | 
				
			||||||
      n.blacken()
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    } else if (n.parent.isBlack()) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var uncle = n.getUncle()
 | 
					 | 
				
			||||||
    if (uncle !== null && uncle.isRed()) {
 | 
					 | 
				
			||||||
      // Note: parent: red, uncle: red
 | 
					 | 
				
			||||||
      n.parent.blacken()
 | 
					 | 
				
			||||||
      uncle.blacken()
 | 
					 | 
				
			||||||
      n.grandparent.redden()
 | 
					 | 
				
			||||||
      this._fixInsert(n.grandparent)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // Note: parent: red, uncle: black or null
 | 
					 | 
				
			||||||
      // Now we transform the tree in such a way that
 | 
					 | 
				
			||||||
      // either of these holds:
 | 
					 | 
				
			||||||
      //   1) grandparent.left.isRed
 | 
					 | 
				
			||||||
      //     and grandparent.left.left.isRed
 | 
					 | 
				
			||||||
      //   2) grandparent.right.isRed
 | 
					 | 
				
			||||||
      //     and grandparent.right.right.isRed
 | 
					 | 
				
			||||||
      if (n === n.parent.right && n.parent === n.grandparent.left) {
 | 
					 | 
				
			||||||
        n.parent.rotateLeft(this)
 | 
					 | 
				
			||||||
        // Since we rotated and want to use the previous
 | 
					 | 
				
			||||||
        // cases, we need to set n in such a way that
 | 
					 | 
				
			||||||
        // n.parent.isRed again
 | 
					 | 
				
			||||||
        n = n.left
 | 
					 | 
				
			||||||
      } else if (n === n.parent.left && n.parent === n.grandparent.right) {
 | 
					 | 
				
			||||||
        n.parent.rotateRight(this)
 | 
					 | 
				
			||||||
        // see above
 | 
					 | 
				
			||||||
        n = n.right
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // Case 1) or 2) hold from here on.
 | 
					      var node = new N(v)
 | 
				
			||||||
      // Now traverse grandparent, make parent a black node
 | 
					      if (this.root !== null) {
 | 
				
			||||||
      // on the highest level which holds two red nodes.
 | 
					        var p = this.root // p abbrev. parent
 | 
				
			||||||
      n.parent.blacken()
 | 
					        while (true) {
 | 
				
			||||||
      n.grandparent.redden()
 | 
					          if (Y.utils.smaller(node.val.id, p.val.id)) {
 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					            if (p.left === null) {
 | 
				
			||||||
        // Case 1
 | 
					              p.left = node
 | 
				
			||||||
        n.grandparent.rotateRight(this)
 | 
					              break
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              p = p.left
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else if (Y.utils.smaller(p.val.id, node.val.id)) {
 | 
				
			||||||
 | 
					            if (p.right === null) {
 | 
				
			||||||
 | 
					              p.right = node
 | 
				
			||||||
 | 
					              break
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              p = p.right
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            p.val = node.val
 | 
				
			||||||
 | 
					            return p
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._fixInsert(node)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // Case 2
 | 
					        this.root = node
 | 
				
			||||||
        n.grandparent.rotateLeft(this)
 | 
					      }
 | 
				
			||||||
 | 
					      this.length++
 | 
				
			||||||
 | 
					      this.root.blacken()
 | 
				
			||||||
 | 
					      return node
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _fixInsert (n) {
 | 
				
			||||||
 | 
					      if (n.parent === null) {
 | 
				
			||||||
 | 
					        n.blacken()
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      } else if (n.parent.isBlack()) {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var uncle = n.getUncle()
 | 
				
			||||||
 | 
					      if (uncle !== null && uncle.isRed()) {
 | 
				
			||||||
 | 
					        // Note: parent: red, uncle: red
 | 
				
			||||||
 | 
					        n.parent.blacken()
 | 
				
			||||||
 | 
					        uncle.blacken()
 | 
				
			||||||
 | 
					        n.grandparent.redden()
 | 
				
			||||||
 | 
					        this._fixInsert(n.grandparent)
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // Note: parent: red, uncle: black or null
 | 
				
			||||||
 | 
					        // Now we transform the tree in such a way that
 | 
				
			||||||
 | 
					        // either of these holds:
 | 
				
			||||||
 | 
					        //   1) grandparent.left.isRed
 | 
				
			||||||
 | 
					        //     and grandparent.left.left.isRed
 | 
				
			||||||
 | 
					        //   2) grandparent.right.isRed
 | 
				
			||||||
 | 
					        //     and grandparent.right.right.isRed
 | 
				
			||||||
 | 
					        if (n === n.parent.right && n.parent === n.grandparent.left) {
 | 
				
			||||||
 | 
					          n.parent.rotateLeft(this)
 | 
				
			||||||
 | 
					          // Since we rotated and want to use the previous
 | 
				
			||||||
 | 
					          // cases, we need to set n in such a way that
 | 
				
			||||||
 | 
					          // n.parent.isRed again
 | 
				
			||||||
 | 
					          n = n.left
 | 
				
			||||||
 | 
					        } else if (n === n.parent.left && n.parent === n.grandparent.right) {
 | 
				
			||||||
 | 
					          n.parent.rotateRight(this)
 | 
				
			||||||
 | 
					          // see above
 | 
				
			||||||
 | 
					          n = n.right
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Case 1) or 2) hold from here on.
 | 
				
			||||||
 | 
					        // Now traverse grandparent, make parent a black node
 | 
				
			||||||
 | 
					        // on the highest level which holds two red nodes.
 | 
				
			||||||
 | 
					        n.parent.blacken()
 | 
				
			||||||
 | 
					        n.grandparent.redden()
 | 
				
			||||||
 | 
					        if (n === n.parent.left) {
 | 
				
			||||||
 | 
					          // Case 1
 | 
				
			||||||
 | 
					          n.grandparent.rotateRight(this)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          // Case 2
 | 
				
			||||||
 | 
					          n.grandparent.rotateLeft(this)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.utils.RBTree = RBTree
 | 
					  Y.utils.RBTree = RBTree
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
/* eslint-env browser,jasmine,console */
 | 
					/* eslint-env browser,jasmine,console */
 | 
				
			||||||
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Y = require('../SpecHelper.js')
 | 
				
			||||||
var numberOfRBTreeTests = 1000
 | 
					var numberOfRBTreeTests = 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function itRedNodesDoNotHaveBlackChildren () {
 | 
					function itRedNodesDoNotHaveBlackChildren () {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,288 +0,0 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
/* eslint-env browser, jasmine */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  This is just a compilation of functions that help to test this library!
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// When testing, you store everything on the global object. We call it g
 | 
					 | 
				
			||||||
var g
 | 
					 | 
				
			||||||
if (typeof global !== 'undefined') {
 | 
					 | 
				
			||||||
  g = global
 | 
					 | 
				
			||||||
} else if (typeof window !== 'undefined') {
 | 
					 | 
				
			||||||
  g = window
 | 
					 | 
				
			||||||
} else {
 | 
					 | 
				
			||||||
  throw new Error('No global object?')
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g.g = g
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.YConcurrency_TestingMode = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.describeManyTimes = function describeManyTimes (times, name, f) {
 | 
					 | 
				
			||||||
  for (var i = 0; i < times; i++) {
 | 
					 | 
				
			||||||
    describe(name, f)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  Wait for a specified amount of time (in ms). defaults to 5ms
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
function wait (t) {
 | 
					 | 
				
			||||||
  if (t == null) {
 | 
					 | 
				
			||||||
    t = 5
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return new Promise(function (resolve) {
 | 
					 | 
				
			||||||
    setTimeout(function () {
 | 
					 | 
				
			||||||
      resolve()
 | 
					 | 
				
			||||||
    }, t * 2)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g.wait = wait
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.databases = ['Memory']
 | 
					 | 
				
			||||||
if (typeof window !== 'undefined') {
 | 
					 | 
				
			||||||
  g.databases.push('IndexedDB')
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  returns a random element of o.
 | 
					 | 
				
			||||||
  works on Object, and Array
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
function getRandom (o) {
 | 
					 | 
				
			||||||
  if (o instanceof Array) {
 | 
					 | 
				
			||||||
    return o[Math.floor(Math.random() * o.length)]
 | 
					 | 
				
			||||||
  } else if (o.constructor === Object) {
 | 
					 | 
				
			||||||
    var ks = []
 | 
					 | 
				
			||||||
    for (var key in o) {
 | 
					 | 
				
			||||||
      ks.push(key)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return o[getRandom(ks)]
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g.getRandom = getRandom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getRandomNumber (n) {
 | 
					 | 
				
			||||||
  if (n == null) {
 | 
					 | 
				
			||||||
    n = 9999
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return Math.floor(Math.random() * n)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g.getRandomNumber = getRandomNumber
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function * applyTransactions (relAmount, numberOfTransactions, objects, users, transactions) {
 | 
					 | 
				
			||||||
  function randomTransaction (root) {
 | 
					 | 
				
			||||||
    var f = getRandom(transactions)
 | 
					 | 
				
			||||||
    f(root)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) {
 | 
					 | 
				
			||||||
    var r = Math.random()
 | 
					 | 
				
			||||||
    if (r >= 0.5) {
 | 
					 | 
				
			||||||
      // 50% chance to flush
 | 
					 | 
				
			||||||
      users[0].connector.flushOne() // flushes for some user.. (not necessarily 0)
 | 
					 | 
				
			||||||
    } else if (r >= 0.05) {
 | 
					 | 
				
			||||||
      // 45% chance to create operation
 | 
					 | 
				
			||||||
      randomTransaction(getRandom(objects))
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // 5% chance to disconnect/reconnect
 | 
					 | 
				
			||||||
      var u = getRandom(users)
 | 
					 | 
				
			||||||
      if (u.connector.isDisconnected()) {
 | 
					 | 
				
			||||||
        yield u.reconnect()
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        yield u.disconnect()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    yield wait()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
 | 
					 | 
				
			||||||
  yield* applyTransactions(1, numberOfTransactions, objects, users, transactions)
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					 | 
				
			||||||
  yield wait()
 | 
					 | 
				
			||||||
  for (var u in users) {
 | 
					 | 
				
			||||||
    yield users[u].reconnect()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
 | 
					 | 
				
			||||||
  yield* applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  for (var u in users) {
 | 
					 | 
				
			||||||
    // TODO: here, we enforce that two users never sync at the same time with u[0]
 | 
					 | 
				
			||||||
    //       enforce that in the connector itself!
 | 
					 | 
				
			||||||
    yield users[u].reconnect()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
 | 
					 | 
				
			||||||
  // gc two times because of the two gc phases (really collect everything)
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
  for (var i in users) {
 | 
					 | 
				
			||||||
    yield users[i].db.garbageCollect()
 | 
					 | 
				
			||||||
    yield users[i].db.garbageCollect()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  yield wait(100)
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.compareAllUsers = async(function * compareAllUsers (users) {
 | 
					 | 
				
			||||||
  var s1, s2 // state sets
 | 
					 | 
				
			||||||
  var ds1, ds2 // delete sets
 | 
					 | 
				
			||||||
  var allDels1, allDels2 // all deletions
 | 
					 | 
				
			||||||
  var db1 = [] // operation store of user1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
 | 
					 | 
				
			||||||
  function * t1 () {
 | 
					 | 
				
			||||||
    s1 = yield* this.getStateSet()
 | 
					 | 
				
			||||||
    ds1 = yield* this.getDeleteSet()
 | 
					 | 
				
			||||||
    allDels1 = []
 | 
					 | 
				
			||||||
    yield* this.ds.iterate(this, null, null, function * (d) {
 | 
					 | 
				
			||||||
      allDels1.push(d)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  function * t2 () {
 | 
					 | 
				
			||||||
    s2 = yield* this.getStateSet()
 | 
					 | 
				
			||||||
    ds2 = yield* this.getDeleteSet()
 | 
					 | 
				
			||||||
    allDels2 = []
 | 
					 | 
				
			||||||
    yield* this.ds.iterate(this, null, null, function * (d) {
 | 
					 | 
				
			||||||
      allDels2.push(d)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  yield users[0].connector.flushAll()
 | 
					 | 
				
			||||||
  yield wait()
 | 
					 | 
				
			||||||
  yield g.garbageCollectAllUsers(users)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (var uid = 0; uid < users.length; uid++) {
 | 
					 | 
				
			||||||
    var u = users[uid]
 | 
					 | 
				
			||||||
    u.db.requestTransaction(function * () {
 | 
					 | 
				
			||||||
      // compare deleted ops against deleteStore
 | 
					 | 
				
			||||||
      yield* this.os.iterate(this, null, null, function * (o) {
 | 
					 | 
				
			||||||
        if (o.deleted === true) {
 | 
					 | 
				
			||||||
          expect(yield* this.isDeleted(o.id)).toBeTruthy()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      // compare deleteStore against deleted ops
 | 
					 | 
				
			||||||
      var ds = []
 | 
					 | 
				
			||||||
      yield* this.ds.iterate(this, null, null, function * (d) {
 | 
					 | 
				
			||||||
        ds.push(d)
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      for (var j in ds) {
 | 
					 | 
				
			||||||
        var d = ds[j]
 | 
					 | 
				
			||||||
        for (var i = 0; i < d.len; i++) {
 | 
					 | 
				
			||||||
          var o = yield* this.getOperation([d.id[0], d.id[1] + i])
 | 
					 | 
				
			||||||
          // gc'd or deleted
 | 
					 | 
				
			||||||
          if (d.gc) {
 | 
					 | 
				
			||||||
            expect(o).toBeFalsy()
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            expect(o.deleted).toBeTruthy()
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    // compare allDels tree
 | 
					 | 
				
			||||||
    yield wait()
 | 
					 | 
				
			||||||
    if (s1 == null) {
 | 
					 | 
				
			||||||
      u.db.requestTransaction(function * () {
 | 
					 | 
				
			||||||
        yield* t1.call(this)
 | 
					 | 
				
			||||||
        yield* this.os.iterate(this, null, null, function * (o) {
 | 
					 | 
				
			||||||
          o = Y.utils.copyObject(o)
 | 
					 | 
				
			||||||
          delete o.origin
 | 
					 | 
				
			||||||
          db1.push(o)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      yield wait()
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // TODO: make requestTransaction return a promise..
 | 
					 | 
				
			||||||
      u.db.requestTransaction(function * () {
 | 
					 | 
				
			||||||
        yield* t2.call(this)
 | 
					 | 
				
			||||||
        expect(s1).toEqual(s2)
 | 
					 | 
				
			||||||
        expect(allDels1).toEqual(allDels2) // inner structure
 | 
					 | 
				
			||||||
        expect(ds1).toEqual(ds2) // exported structure
 | 
					 | 
				
			||||||
        var count = 0
 | 
					 | 
				
			||||||
        yield* this.os.iterate(this, null, null, function * (o) {
 | 
					 | 
				
			||||||
          o = Y.utils.copyObject(o)
 | 
					 | 
				
			||||||
          delete o.origin
 | 
					 | 
				
			||||||
          expect(db1[count++]).toEqual(o)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      yield wait()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.createUsers = async(function * createUsers (self, numberOfUsers, database) {
 | 
					 | 
				
			||||||
  if (Y.utils.globalRoom.users[0] != null) {
 | 
					 | 
				
			||||||
    yield Y.utils.globalRoom.users[0].flushAll()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // destroy old users
 | 
					 | 
				
			||||||
  for (var u in Y.utils.globalRoom.users) {
 | 
					 | 
				
			||||||
    Y.utils.globalRoom.users[u].y.destroy()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  self.users = null
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var promises = []
 | 
					 | 
				
			||||||
  for (var i = 0; i < numberOfUsers; i++) {
 | 
					 | 
				
			||||||
    promises.push(Y({
 | 
					 | 
				
			||||||
      db: {
 | 
					 | 
				
			||||||
        name: database,
 | 
					 | 
				
			||||||
        namespace: 'User ' + i,
 | 
					 | 
				
			||||||
        cleanStart: true,
 | 
					 | 
				
			||||||
        gcTimeout: -1
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      connector: {
 | 
					 | 
				
			||||||
        name: 'Test',
 | 
					 | 
				
			||||||
        debug: false
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  self.users = yield Promise.all(promises)
 | 
					 | 
				
			||||||
  return self.users
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  Until async/await arrives in js, we use this function to wait for promises
 | 
					 | 
				
			||||||
  by yielding them.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
function async (makeGenerator) {
 | 
					 | 
				
			||||||
  return function (arg) {
 | 
					 | 
				
			||||||
    var generator = makeGenerator.apply(this, arguments)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function handle (result) {
 | 
					 | 
				
			||||||
      if (result.done) return Promise.resolve(result.value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return Promise.resolve(result.value).then(function (res) {
 | 
					 | 
				
			||||||
        return handle(generator.next(res))
 | 
					 | 
				
			||||||
      }, function (err) {
 | 
					 | 
				
			||||||
        return handle(generator.throw(err))
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      return handle(generator.next())
 | 
					 | 
				
			||||||
    } catch (ex) {
 | 
					 | 
				
			||||||
      generator.throw(ex)
 | 
					 | 
				
			||||||
      // return Promise.reject(ex)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
g.async = async
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function logUsers (self) {
 | 
					 | 
				
			||||||
  if (self.constructor === Array) {
 | 
					 | 
				
			||||||
    self = {users: self}
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  self.users[0].db.logTable()
 | 
					 | 
				
			||||||
  self.users[1].db.logTable()
 | 
					 | 
				
			||||||
  self.users[2].db.logTable()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
g.logUsers = logUsers
 | 
					 | 
				
			||||||
							
								
								
									
										590
									
								
								src/Struct.js
									
									
									
									
									
								
							
							
						
						
									
										590
									
								
								src/Struct.js
									
									
									
									
									
								
							@ -1,4 +1,3 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
@ -19,318 +18,319 @@
 | 
				
			|||||||
 * requiredOps
 | 
					 * requiredOps
 | 
				
			||||||
     - Operations that are required to execute this operation.
 | 
					     - Operations that are required to execute this operation.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					module.exports = function (Y) {
 | 
				
			||||||
 | 
					  var Struct = {
 | 
				
			||||||
 | 
					    /* This is the only operation that is actually not a structure, because
 | 
				
			||||||
 | 
					    it is not stored in the OS. This is why it _does not_ have an id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var Struct = {
 | 
					    op = {
 | 
				
			||||||
  /* This is the only operation that is actually not a structure, because
 | 
					      target: Id
 | 
				
			||||||
  it is not stored in the OS. This is why it _does not_ have an id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  op = {
 | 
					 | 
				
			||||||
    target: Id
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  Delete: {
 | 
					 | 
				
			||||||
    encode: function (op) {
 | 
					 | 
				
			||||||
      return op
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    requiredOps: function (op) {
 | 
					 | 
				
			||||||
      return [] // [op.target]
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    execute: function * (op) {
 | 
					 | 
				
			||||||
      return yield* this.deleteOperation(op.target)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  Insert: {
 | 
					 | 
				
			||||||
    /* {
 | 
					 | 
				
			||||||
        content: any,
 | 
					 | 
				
			||||||
        id: Id,
 | 
					 | 
				
			||||||
        left: Id,
 | 
					 | 
				
			||||||
        origin: Id,
 | 
					 | 
				
			||||||
        right: Id,
 | 
					 | 
				
			||||||
        parent: Id,
 | 
					 | 
				
			||||||
        parentSub: string (optional), // child of Map type
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    encode: function (op) {
 | 
					 | 
				
			||||||
      // TODO: you could not send the "left" property, then you also have to
 | 
					 | 
				
			||||||
      // "op.left = null" in $execute or $decode
 | 
					 | 
				
			||||||
      var e = {
 | 
					 | 
				
			||||||
        id: op.id,
 | 
					 | 
				
			||||||
        left: op.left,
 | 
					 | 
				
			||||||
        right: op.right,
 | 
					 | 
				
			||||||
        origin: op.origin,
 | 
					 | 
				
			||||||
        parent: op.parent,
 | 
					 | 
				
			||||||
        struct: op.struct
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (op.parentSub != null) {
 | 
					 | 
				
			||||||
        e.parentSub = op.parentSub
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (op.opContent != null) {
 | 
					 | 
				
			||||||
        e.opContent = op.opContent
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        e.content = op.content
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return e
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    requiredOps: function (op) {
 | 
					 | 
				
			||||||
      var ids = []
 | 
					 | 
				
			||||||
      if (op.left != null) {
 | 
					 | 
				
			||||||
        ids.push(op.left)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (op.right != null) {
 | 
					 | 
				
			||||||
        ids.push(op.right)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (op.origin != null && !Y.utils.compareIds(op.left, op.origin)) {
 | 
					 | 
				
			||||||
        ids.push(op.origin)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // if (op.right == null && op.left == null) {
 | 
					 | 
				
			||||||
      ids.push(op.parent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (op.opContent != null) {
 | 
					 | 
				
			||||||
        ids.push(op.opContent)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return ids
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    getDistanceToOrigin: function * (op) {
 | 
					 | 
				
			||||||
      if (op.left == null) {
 | 
					 | 
				
			||||||
        return 0
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        var d = 0
 | 
					 | 
				
			||||||
        var o = yield* this.getOperation(op.left)
 | 
					 | 
				
			||||||
        while (!Y.utils.compareIds(op.origin, (o ? o.id : null))) {
 | 
					 | 
				
			||||||
          d++
 | 
					 | 
				
			||||||
          if (o.left == null) {
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            o = yield* this.getOperation(o.left)
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return d
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    # $this has to find a unique position between origin and the next known character
 | 
					 | 
				
			||||||
    # case 1: $origin equals $o.origin: the $creator parameter decides if left or right
 | 
					 | 
				
			||||||
    #         let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
 | 
					 | 
				
			||||||
    #         o2,o3 and o4 origin is 1 (the position of o2)
 | 
					 | 
				
			||||||
    #         there is the case that $this.creator < o2.creator, but o3.creator < $this.creator
 | 
					 | 
				
			||||||
    #         then o2 knows o3. Since on another client $OL could be [o1,o3,o4] the problem is complex
 | 
					 | 
				
			||||||
    #         therefore $this would be always to the right of o3
 | 
					 | 
				
			||||||
    # case 2: $origin < $o.origin
 | 
					 | 
				
			||||||
    #         if current $this insert_position > $o origin: $this ins
 | 
					 | 
				
			||||||
    #         else $insert_position will not change
 | 
					 | 
				
			||||||
    #         (maybe we encounter case 1 later, then this will be to the right of $o)
 | 
					 | 
				
			||||||
    # case 3: $origin > $o.origin
 | 
					 | 
				
			||||||
    #         $this insert_position is to the left of $o (forever!)
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    execute: function *(op) {
 | 
					 | 
				
			||||||
      var i // loop counter
 | 
					 | 
				
			||||||
      var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
 | 
					 | 
				
			||||||
      var o
 | 
					 | 
				
			||||||
      var parent
 | 
					 | 
				
			||||||
      var start
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // find o. o is the first conflicting operation
 | 
					 | 
				
			||||||
      if (op.left != null) {
 | 
					 | 
				
			||||||
        o = yield* this.getOperation(op.left)
 | 
					 | 
				
			||||||
        o = (o.right == null) ? null : yield* this.getOperation(o.right)
 | 
					 | 
				
			||||||
      } else { // left == null
 | 
					 | 
				
			||||||
        parent = yield* this.getOperation(op.parent)
 | 
					 | 
				
			||||||
        let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
 | 
					 | 
				
			||||||
        start = startId == null ? null : yield* this.getOperation(startId)
 | 
					 | 
				
			||||||
        o = start
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // handle conflicts
 | 
					 | 
				
			||||||
      while (true) {
 | 
					 | 
				
			||||||
        if (o != null && !Y.utils.compareIds(o.id, op.right)) {
 | 
					 | 
				
			||||||
          var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
 | 
					 | 
				
			||||||
          if (oOriginDistance === i) {
 | 
					 | 
				
			||||||
            // case 1
 | 
					 | 
				
			||||||
            if (o.id[0] < op.id[0]) {
 | 
					 | 
				
			||||||
              op.left = o.id
 | 
					 | 
				
			||||||
              distanceToOrigin = i + 1
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          } else if (oOriginDistance < i) {
 | 
					 | 
				
			||||||
            // case 2
 | 
					 | 
				
			||||||
            if (i - distanceToOrigin <= oOriginDistance) {
 | 
					 | 
				
			||||||
              op.left = o.id
 | 
					 | 
				
			||||||
              distanceToOrigin = i + 1
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          i++
 | 
					 | 
				
			||||||
          o = o.right ? yield* this.getOperation(o.right) : null
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          break
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // reconnect..
 | 
					 | 
				
			||||||
      var left = null
 | 
					 | 
				
			||||||
      var right = null
 | 
					 | 
				
			||||||
      parent = parent || (yield* this.getOperation(op.parent))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // reconnect left and set right of op
 | 
					 | 
				
			||||||
      if (op.left != null) {
 | 
					 | 
				
			||||||
        left = yield* this.getOperation(op.left)
 | 
					 | 
				
			||||||
        op.right = left.right
 | 
					 | 
				
			||||||
        left.right = op.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        yield* this.setOperation(left)
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // reconnect right
 | 
					 | 
				
			||||||
      if (op.right != null) {
 | 
					 | 
				
			||||||
        right = yield* this.getOperation(op.right)
 | 
					 | 
				
			||||||
        right.left = op.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // if right exists, and it is supposed to be gc'd. Remove it from the gc
 | 
					 | 
				
			||||||
        if (right.gc != null) {
 | 
					 | 
				
			||||||
          this.store.removeFromGarbageCollector(right)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        yield* this.setOperation(right)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // update parents .map/start/end properties
 | 
					 | 
				
			||||||
      if (op.parentSub != null) {
 | 
					 | 
				
			||||||
        if (left == null) {
 | 
					 | 
				
			||||||
          parent.map[op.parentSub] = op.id
 | 
					 | 
				
			||||||
          yield* this.setOperation(parent)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // is a child of a map struct.
 | 
					 | 
				
			||||||
        // Then also make sure that only the most left element is not deleted
 | 
					 | 
				
			||||||
        if (op.right != null) {
 | 
					 | 
				
			||||||
          yield* this.deleteOperation(op.right, true)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (op.left != null) {
 | 
					 | 
				
			||||||
          yield* this.deleteOperation(op.id, true)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        if (right == null || left == null) {
 | 
					 | 
				
			||||||
          if (right == null) {
 | 
					 | 
				
			||||||
            parent.end = op.id
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          if (left == null) {
 | 
					 | 
				
			||||||
            parent.start = op.id
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          yield* this.setOperation(parent)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  List: {
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      start: null,
 | 
					 | 
				
			||||||
      end: null,
 | 
					 | 
				
			||||||
      struct: "List",
 | 
					 | 
				
			||||||
      type: "",
 | 
					 | 
				
			||||||
      id: this.os.getNextOpId()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    encode: function (op) {
 | 
					    Delete: {
 | 
				
			||||||
      return {
 | 
					      encode: function (op) {
 | 
				
			||||||
        struct: 'List',
 | 
					        return op
 | 
				
			||||||
        id: op.id,
 | 
					      },
 | 
				
			||||||
        type: op.type
 | 
					      requiredOps: function (op) {
 | 
				
			||||||
 | 
					        return [] // [op.target]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      execute: function * (op) {
 | 
				
			||||||
 | 
					        return yield* this.deleteOperation(op.target)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function () {
 | 
					    Insert: {
 | 
				
			||||||
      /*
 | 
					      /* {
 | 
				
			||||||
      var ids = []
 | 
					          content: any,
 | 
				
			||||||
      if (op.start != null) {
 | 
					          id: Id,
 | 
				
			||||||
        ids.push(op.start)
 | 
					          left: Id,
 | 
				
			||||||
      }
 | 
					          origin: Id,
 | 
				
			||||||
      if (op.end != null){
 | 
					          right: Id,
 | 
				
			||||||
        ids.push(op.end)
 | 
					          parent: Id,
 | 
				
			||||||
      }
 | 
					          parentSub: string (optional), // child of Map type
 | 
				
			||||||
      return ids
 | 
					        }
 | 
				
			||||||
      */
 | 
					      */
 | 
				
			||||||
      return []
 | 
					      encode: function (op) {
 | 
				
			||||||
    },
 | 
					        // TODO: you could not send the "left" property, then you also have to
 | 
				
			||||||
    execute: function * (op) {
 | 
					        // "op.left = null" in $execute or $decode
 | 
				
			||||||
      op.start = null
 | 
					        var e = {
 | 
				
			||||||
      op.end = null
 | 
					          id: op.id,
 | 
				
			||||||
    },
 | 
					          left: op.left,
 | 
				
			||||||
    ref: function * (op, pos) {
 | 
					          right: op.right,
 | 
				
			||||||
      if (op.start == null) {
 | 
					          origin: op.origin,
 | 
				
			||||||
        return null
 | 
					          parent: op.parent,
 | 
				
			||||||
      }
 | 
					          struct: op.struct
 | 
				
			||||||
      var res = null
 | 
					 | 
				
			||||||
      var o = yield* this.getOperation(op.start)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      while (true) {
 | 
					 | 
				
			||||||
        if (!o.deleted) {
 | 
					 | 
				
			||||||
          res = o
 | 
					 | 
				
			||||||
          pos--
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (pos >= 0 && o.right != null) {
 | 
					        if (op.parentSub != null) {
 | 
				
			||||||
          o = (yield* this.getOperation(o.right))
 | 
					          e.parentSub = op.parentSub
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (op.opContent != null) {
 | 
				
			||||||
 | 
					          e.opContent = op.opContent
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          break
 | 
					          e.content = op.content
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return e
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      requiredOps: function (op) {
 | 
				
			||||||
 | 
					        var ids = []
 | 
				
			||||||
 | 
					        if (op.left != null) {
 | 
				
			||||||
 | 
					          ids.push(op.left)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (op.right != null) {
 | 
				
			||||||
 | 
					          ids.push(op.right)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (op.origin != null && !Y.utils.compareIds(op.left, op.origin)) {
 | 
				
			||||||
 | 
					          ids.push(op.origin)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // if (op.right == null && op.left == null) {
 | 
				
			||||||
 | 
					        ids.push(op.parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (op.opContent != null) {
 | 
				
			||||||
 | 
					          ids.push(op.opContent)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ids
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      getDistanceToOrigin: function * (op) {
 | 
				
			||||||
 | 
					        if (op.left == null) {
 | 
				
			||||||
 | 
					          return 0
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          var d = 0
 | 
				
			||||||
 | 
					          var o = yield* this.getOperation(op.left)
 | 
				
			||||||
 | 
					          while (!Y.utils.compareIds(op.origin, (o ? o.id : null))) {
 | 
				
			||||||
 | 
					            d++
 | 
				
			||||||
 | 
					            if (o.left == null) {
 | 
				
			||||||
 | 
					              break
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              o = yield* this.getOperation(o.left)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return d
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      /*
 | 
				
			||||||
 | 
					      # $this has to find a unique position between origin and the next known character
 | 
				
			||||||
 | 
					      # case 1: $origin equals $o.origin: the $creator parameter decides if left or right
 | 
				
			||||||
 | 
					      #         let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
 | 
				
			||||||
 | 
					      #         o2,o3 and o4 origin is 1 (the position of o2)
 | 
				
			||||||
 | 
					      #         there is the case that $this.creator < o2.creator, but o3.creator < $this.creator
 | 
				
			||||||
 | 
					      #         then o2 knows o3. Since on another client $OL could be [o1,o3,o4] the problem is complex
 | 
				
			||||||
 | 
					      #         therefore $this would be always to the right of o3
 | 
				
			||||||
 | 
					      # case 2: $origin < $o.origin
 | 
				
			||||||
 | 
					      #         if current $this insert_position > $o origin: $this ins
 | 
				
			||||||
 | 
					      #         else $insert_position will not change
 | 
				
			||||||
 | 
					      #         (maybe we encounter case 1 later, then this will be to the right of $o)
 | 
				
			||||||
 | 
					      # case 3: $origin > $o.origin
 | 
				
			||||||
 | 
					      #         $this insert_position is to the left of $o (forever!)
 | 
				
			||||||
 | 
					      */
 | 
				
			||||||
 | 
					      execute: function *(op) {
 | 
				
			||||||
 | 
					        var i // loop counter
 | 
				
			||||||
 | 
					        var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
 | 
				
			||||||
 | 
					        var o
 | 
				
			||||||
 | 
					        var parent
 | 
				
			||||||
 | 
					        var start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // find o. o is the first conflicting operation
 | 
				
			||||||
 | 
					        if (op.left != null) {
 | 
				
			||||||
 | 
					          o = yield* this.getOperation(op.left)
 | 
				
			||||||
 | 
					          o = (o.right == null) ? null : yield* this.getOperation(o.right)
 | 
				
			||||||
 | 
					        } else { // left == null
 | 
				
			||||||
 | 
					          parent = yield* this.getOperation(op.parent)
 | 
				
			||||||
 | 
					          let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
 | 
				
			||||||
 | 
					          start = startId == null ? null : yield* this.getOperation(startId)
 | 
				
			||||||
 | 
					          o = start
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // handle conflicts
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					          if (o != null && !Y.utils.compareIds(o.id, op.right)) {
 | 
				
			||||||
 | 
					            var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
 | 
				
			||||||
 | 
					            if (oOriginDistance === i) {
 | 
				
			||||||
 | 
					              // case 1
 | 
				
			||||||
 | 
					              if (o.id[0] < op.id[0]) {
 | 
				
			||||||
 | 
					                op.left = o.id
 | 
				
			||||||
 | 
					                distanceToOrigin = i + 1
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else if (oOriginDistance < i) {
 | 
				
			||||||
 | 
					              // case 2
 | 
				
			||||||
 | 
					              if (i - distanceToOrigin <= oOriginDistance) {
 | 
				
			||||||
 | 
					                op.left = o.id
 | 
				
			||||||
 | 
					                distanceToOrigin = i + 1
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              break
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            i++
 | 
				
			||||||
 | 
					            o = o.right ? yield* this.getOperation(o.right) : null
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // reconnect..
 | 
				
			||||||
 | 
					        var left = null
 | 
				
			||||||
 | 
					        var right = null
 | 
				
			||||||
 | 
					        parent = parent || (yield* this.getOperation(op.parent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // reconnect left and set right of op
 | 
				
			||||||
 | 
					        if (op.left != null) {
 | 
				
			||||||
 | 
					          left = yield* this.getOperation(op.left)
 | 
				
			||||||
 | 
					          op.right = left.right
 | 
				
			||||||
 | 
					          left.right = op.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          yield* this.setOperation(left)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // reconnect right
 | 
				
			||||||
 | 
					        if (op.right != null) {
 | 
				
			||||||
 | 
					          right = yield* this.getOperation(op.right)
 | 
				
			||||||
 | 
					          right.left = op.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // if right exists, and it is supposed to be gc'd. Remove it from the gc
 | 
				
			||||||
 | 
					          if (right.gc != null) {
 | 
				
			||||||
 | 
					            this.store.removeFromGarbageCollector(right)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          yield* this.setOperation(right)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // update parents .map/start/end properties
 | 
				
			||||||
 | 
					        if (op.parentSub != null) {
 | 
				
			||||||
 | 
					          if (left == null) {
 | 
				
			||||||
 | 
					            parent.map[op.parentSub] = op.id
 | 
				
			||||||
 | 
					            yield* this.setOperation(parent)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          // is a child of a map struct.
 | 
				
			||||||
 | 
					          // Then also make sure that only the most left element is not deleted
 | 
				
			||||||
 | 
					          if (op.right != null) {
 | 
				
			||||||
 | 
					            yield* this.deleteOperation(op.right, true)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (op.left != null) {
 | 
				
			||||||
 | 
					            yield* this.deleteOperation(op.id, true)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          if (right == null || left == null) {
 | 
				
			||||||
 | 
					            if (right == null) {
 | 
				
			||||||
 | 
					              parent.end = op.id
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (left == null) {
 | 
				
			||||||
 | 
					              parent.start = op.id
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            yield* this.setOperation(parent)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return res
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    map: function * (o, f) {
 | 
					    List: {
 | 
				
			||||||
      o = o.start
 | 
					      /*
 | 
				
			||||||
      var res = []
 | 
					 | 
				
			||||||
      while (o != null) { // TODO: change to != (at least some convention)
 | 
					 | 
				
			||||||
        var operation = yield* this.getOperation(o)
 | 
					 | 
				
			||||||
        if (!operation.deleted) {
 | 
					 | 
				
			||||||
          res.push(f(operation))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        o = operation.right
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return res
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  Map: {
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        map: {},
 | 
					        start: null,
 | 
				
			||||||
        struct: "Map",
 | 
					        end: null,
 | 
				
			||||||
 | 
					        struct: "List",
 | 
				
			||||||
        type: "",
 | 
					        type: "",
 | 
				
			||||||
        id: this.os.getNextOpId()
 | 
					        id: this.os.getNextOpId()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    */
 | 
					      */
 | 
				
			||||||
    encode: function (op) {
 | 
					      encode: function (op) {
 | 
				
			||||||
      return {
 | 
					        return {
 | 
				
			||||||
        struct: 'Map',
 | 
					          struct: 'List',
 | 
				
			||||||
        type: op.type,
 | 
					          id: op.id,
 | 
				
			||||||
        id: op.id,
 | 
					          type: op.type
 | 
				
			||||||
        map: {} // overwrite map!!
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      requiredOps: function () {
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					        var ids = []
 | 
				
			||||||
 | 
					        if (op.start != null) {
 | 
				
			||||||
 | 
					          ids.push(op.start)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (op.end != null){
 | 
				
			||||||
 | 
					          ids.push(op.end)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ids
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
 | 
					        return []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      execute: function * (op) {
 | 
				
			||||||
 | 
					        op.start = null
 | 
				
			||||||
 | 
					        op.end = null
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      ref: function * (op, pos) {
 | 
				
			||||||
 | 
					        if (op.start == null) {
 | 
				
			||||||
 | 
					          return null
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var res = null
 | 
				
			||||||
 | 
					        var o = yield* this.getOperation(op.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					          if (!o.deleted) {
 | 
				
			||||||
 | 
					            res = o
 | 
				
			||||||
 | 
					            pos--
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (pos >= 0 && o.right != null) {
 | 
				
			||||||
 | 
					            o = (yield* this.getOperation(o.right))
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      map: function * (o, f) {
 | 
				
			||||||
 | 
					        o = o.start
 | 
				
			||||||
 | 
					        var res = []
 | 
				
			||||||
 | 
					        while (o != null) { // TODO: change to != (at least some convention)
 | 
				
			||||||
 | 
					          var operation = yield* this.getOperation(o)
 | 
				
			||||||
 | 
					          if (!operation.deleted) {
 | 
				
			||||||
 | 
					            res.push(f(operation))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          o = operation.right
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function () {
 | 
					    Map: {
 | 
				
			||||||
      return []
 | 
					      /*
 | 
				
			||||||
    },
 | 
					        {
 | 
				
			||||||
    execute: function * () {},
 | 
					          map: {},
 | 
				
			||||||
    /*
 | 
					          struct: "Map",
 | 
				
			||||||
      Get a property by name
 | 
					          type: "",
 | 
				
			||||||
    */
 | 
					          id: this.os.getNextOpId()
 | 
				
			||||||
    get: function * (op, name) {
 | 
					        }
 | 
				
			||||||
      var oid = op.map[name]
 | 
					      */
 | 
				
			||||||
      if (oid != null) {
 | 
					      encode: function (op) {
 | 
				
			||||||
        var res = yield* this.getOperation(oid)
 | 
					        return {
 | 
				
			||||||
        return (res == null || res.deleted) ? void 0 : (res.opContent == null
 | 
					          struct: 'Map',
 | 
				
			||||||
          ? res.content : yield* this.getType(res.opContent))
 | 
					          type: op.type,
 | 
				
			||||||
      }
 | 
					          id: op.id,
 | 
				
			||||||
    },
 | 
					          map: {} // overwrite map!!
 | 
				
			||||||
    /*
 | 
					        }
 | 
				
			||||||
      Delete a property by name
 | 
					      },
 | 
				
			||||||
    */
 | 
					      requiredOps: function () {
 | 
				
			||||||
    delete: function * (op, name) {
 | 
					        return []
 | 
				
			||||||
      var v = op.map[name] || null
 | 
					      },
 | 
				
			||||||
      if (v != null) {
 | 
					      execute: function * () {},
 | 
				
			||||||
        yield* Struct.Delete.create.call(this, {
 | 
					      /*
 | 
				
			||||||
          target: v
 | 
					        Get a property by name
 | 
				
			||||||
        })
 | 
					      */
 | 
				
			||||||
 | 
					      get: function * (op, name) {
 | 
				
			||||||
 | 
					        var oid = op.map[name]
 | 
				
			||||||
 | 
					        if (oid != null) {
 | 
				
			||||||
 | 
					          var res = yield* this.getOperation(oid)
 | 
				
			||||||
 | 
					          return (res == null || res.deleted) ? void 0 : (res.opContent == null
 | 
				
			||||||
 | 
					            ? res.content : yield* this.getType(res.opContent))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      /*
 | 
				
			||||||
 | 
					        Delete a property by name
 | 
				
			||||||
 | 
					      */
 | 
				
			||||||
 | 
					      delete: function * (op, name) {
 | 
				
			||||||
 | 
					        var v = op.map[name] || null
 | 
				
			||||||
 | 
					        if (v != null) {
 | 
				
			||||||
 | 
					          yield* Struct.Delete.create.call(this, {
 | 
				
			||||||
 | 
					            target: v
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  Y.Struct = Struct
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
Y.Struct = Struct
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1065
									
								
								src/Transaction.js
									
									
									
									
									
								
							
							
						
						
									
										1065
									
								
								src/Transaction.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,7 +1,6 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;(function () {
 | 
					function extend (Y) {
 | 
				
			||||||
  class YArray {
 | 
					  class YArray {
 | 
				
			||||||
    constructor (os, _model, idArray, valArray) {
 | 
					    constructor (os, _model, idArray, valArray) {
 | 
				
			||||||
      this.os = os
 | 
					      this.os = os
 | 
				
			||||||
@ -166,7 +165,7 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Y.Array = new Y.utils.CustomType({
 | 
					  Y.extend('Array', new Y.utils.CustomType({
 | 
				
			||||||
    class: YArray,
 | 
					    class: YArray,
 | 
				
			||||||
    createType: function * YArrayCreator () {
 | 
					    createType: function * YArrayCreator () {
 | 
				
			||||||
      var modelid = this.store.getNextOpId()
 | 
					      var modelid = this.store.getNextOpId()
 | 
				
			||||||
@ -188,5 +187,11 @@
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      return new YArray(os, model.id, idArray, valArray)
 | 
					      return new YArray(os, model.id, idArray, valArray)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  }))
 | 
				
			||||||
})()
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (typeof Y !== 'undefined') {
 | 
				
			||||||
 | 
					  extend(Y)
 | 
				
			||||||
 | 
					} else {
 | 
				
			||||||
 | 
					  module.exports = extend
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
/* global createUsers, databases, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */
 | 
					/* global createUsers, databases, wait, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */
 | 
				
			||||||
/* eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Y = require('../SpecHelper.js')
 | 
				
			||||||
var numberOfYArrayTests = 10
 | 
					var numberOfYArrayTests = 10
 | 
				
			||||||
var repeatArrayTests = 2
 | 
					var repeatArrayTests = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;(function () {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  class YMap {
 | 
					  class YMap {
 | 
				
			||||||
    constructor (os, model, contents, opContents) {
 | 
					    constructor (os, model, contents, opContents) {
 | 
				
			||||||
      this._model = model.id
 | 
					      this._model = model.id
 | 
				
			||||||
@ -292,4 +291,4 @@
 | 
				
			|||||||
      return new YMap(os, model, contents, opContents)
 | 
					      return new YMap(os, model, contents, opContents)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})()
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
/* global createUsers, Y, databases, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */
 | 
					/* global createUsers, databases, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */
 | 
				
			||||||
/* eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Y = require('../SpecHelper.js')
 | 
				
			||||||
var numberOfYMapTests = 10
 | 
					var numberOfYMapTests = 10
 | 
				
			||||||
var repeatMapTeasts = 1
 | 
					var repeatMapTeasts = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;(function () {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  class YTextBind extends Y.Array['class'] {
 | 
					  class YTextBind extends Y.Array['class'] {
 | 
				
			||||||
    constructor (os, _model, idArray, valArray) {
 | 
					    constructor (os, _model, idArray, valArray) {
 | 
				
			||||||
      super(os, _model, idArray, valArray)
 | 
					      super(os, _model, idArray, valArray)
 | 
				
			||||||
@ -287,4 +286,4 @@
 | 
				
			|||||||
      return new YTextBind(os, model.id, idArray, valArray)
 | 
					      return new YTextBind(os, model.id, idArray, valArray)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})()
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										303
									
								
								src/Utils.js
									
									
									
									
									
								
							
							
						
						
									
										303
									
								
								src/Utils.js
									
									
									
									
									
								
							@ -1,4 +1,3 @@
 | 
				
			|||||||
/* global Y */
 | 
					 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
@ -21,178 +20,182 @@
 | 
				
			|||||||
  database request to finish). EventHandler will help you to make your type
 | 
					  database request to finish). EventHandler will help you to make your type
 | 
				
			||||||
  synchronously.
 | 
					  synchronously.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class EventHandler {
 | 
					module.exports = function (Y) {
 | 
				
			||||||
  /*
 | 
					  Y.utils = {}
 | 
				
			||||||
    onevent: is called when the structure changes.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Note: "awaiting opertations" is used to denote operations that were
 | 
					  class EventHandler {
 | 
				
			||||||
    prematurely called. Events for received operations can not be executed until
 | 
					    /*
 | 
				
			||||||
    all prematurely called operations were executed ("waiting operations")
 | 
					      onevent: is called when the structure changes.
 | 
				
			||||||
  */
 | 
					
 | 
				
			||||||
  constructor (onevent) {
 | 
					      Note: "awaiting opertations" is used to denote operations that were
 | 
				
			||||||
    this.waiting = []
 | 
					      prematurely called. Events for received operations can not be executed until
 | 
				
			||||||
    this.awaiting = 0
 | 
					      all prematurely called operations were executed ("waiting operations")
 | 
				
			||||||
    this.onevent = onevent
 | 
					    */
 | 
				
			||||||
    this.eventListeners = []
 | 
					    constructor (onevent) {
 | 
				
			||||||
  }
 | 
					      this.waiting = []
 | 
				
			||||||
  /*
 | 
					      this.awaiting = 0
 | 
				
			||||||
    Call this when a new operation arrives. It will be executed right away if
 | 
					      this.onevent = onevent
 | 
				
			||||||
    there are no waiting operations, that you prematurely executed
 | 
					      this.eventListeners = []
 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  receivedOp (op) {
 | 
					 | 
				
			||||||
    if (this.awaiting <= 0) {
 | 
					 | 
				
			||||||
      this.onevent([op])
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.waiting.push(Y.utils.copyObject(op))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    /*
 | 
				
			||||||
  /*
 | 
					      Call this when a new operation arrives. It will be executed right away if
 | 
				
			||||||
    You created some operations, and you want the `onevent` function to be
 | 
					      there are no waiting operations, that you prematurely executed
 | 
				
			||||||
    called right away. Received operations will not be executed untill all
 | 
					    */
 | 
				
			||||||
    prematurely called operations are executed
 | 
					    receivedOp (op) {
 | 
				
			||||||
  */
 | 
					      if (this.awaiting <= 0) {
 | 
				
			||||||
  awaitAndPrematurelyCall (ops) {
 | 
					        this.onevent([op])
 | 
				
			||||||
    this.awaiting++
 | 
					      } else {
 | 
				
			||||||
    this.onevent(ops)
 | 
					        this.waiting.push(Y.utils.copyObject(op))
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
    Basic event listener boilerplate...
 | 
					 | 
				
			||||||
    TODO: maybe put this in a different type..
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  addEventListener (f) {
 | 
					 | 
				
			||||||
    this.eventListeners.push(f)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  removeEventListener (f) {
 | 
					 | 
				
			||||||
    this.eventListeners = this.eventListeners.filter(function (g) {
 | 
					 | 
				
			||||||
      return f !== g
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  removeAllEventListeners () {
 | 
					 | 
				
			||||||
    this.eventListeners = []
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  callEventListeners (event) {
 | 
					 | 
				
			||||||
    for (var i in this.eventListeners) {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        this.eventListeners[i](event)
 | 
					 | 
				
			||||||
      } catch (e) {
 | 
					 | 
				
			||||||
        console.log('User events must not throw Errors!') // eslint-disable-line
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					    /*
 | 
				
			||||||
  /*
 | 
					      You created some operations, and you want the `onevent` function to be
 | 
				
			||||||
    Call this when you successfully awaited the execution of n Insert operations
 | 
					      called right away. Received operations will not be executed untill all
 | 
				
			||||||
  */
 | 
					      prematurely called operations are executed
 | 
				
			||||||
  awaitedInserts (n) {
 | 
					    */
 | 
				
			||||||
    var ops = this.waiting.splice(this.waiting.length - n)
 | 
					    awaitAndPrematurelyCall (ops) {
 | 
				
			||||||
    for (var oid = 0; oid < ops.length; oid++) {
 | 
					      this.awaiting++
 | 
				
			||||||
      var op = ops[oid]
 | 
					      this.onevent(ops)
 | 
				
			||||||
      for (var i = this.waiting.length - 1; i >= 0; i--) {
 | 
					    }
 | 
				
			||||||
        let w = this.waiting[i]
 | 
					    /*
 | 
				
			||||||
        if (Y.utils.compareIds(op.left, w.id)) {
 | 
					      Basic event listener boilerplate...
 | 
				
			||||||
          // include the effect of op in w
 | 
					      TODO: maybe put this in a different type..
 | 
				
			||||||
          w.right = op.id
 | 
					    */
 | 
				
			||||||
          // exclude the effect of w in op
 | 
					    addEventListener (f) {
 | 
				
			||||||
          op.left = w.left
 | 
					      this.eventListeners.push(f)
 | 
				
			||||||
        } else if (Y.utils.compareIds(op.right, w.id)) {
 | 
					    }
 | 
				
			||||||
          // similar..
 | 
					    removeEventListener (f) {
 | 
				
			||||||
          w.left = op.id
 | 
					      this.eventListeners = this.eventListeners.filter(function (g) {
 | 
				
			||||||
          op.right = w.right
 | 
					        return f !== g
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    removeAllEventListeners () {
 | 
				
			||||||
 | 
					      this.eventListeners = []
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    callEventListeners (event) {
 | 
				
			||||||
 | 
					      for (var i in this.eventListeners) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          this.eventListeners[i](event)
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          console.log('User events must not throw Errors!') // eslint-disable-line
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this._tryCallEvents()
 | 
					    /*
 | 
				
			||||||
  }
 | 
					      Call this when you successfully awaited the execution of n Insert operations
 | 
				
			||||||
  /*
 | 
					    */
 | 
				
			||||||
    Call this when you successfully awaited the execution of n Delete operations
 | 
					    awaitedInserts (n) {
 | 
				
			||||||
  */
 | 
					      var ops = this.waiting.splice(this.waiting.length - n)
 | 
				
			||||||
  awaitedDeletes (n, newLeft) {
 | 
					      for (var oid = 0; oid < ops.length; oid++) {
 | 
				
			||||||
    var ops = this.waiting.splice(this.waiting.length - n)
 | 
					        var op = ops[oid]
 | 
				
			||||||
    for (var j in ops) {
 | 
					        for (var i = this.waiting.length - 1; i >= 0; i--) {
 | 
				
			||||||
      var del = ops[j]
 | 
					 | 
				
			||||||
      if (newLeft != null) {
 | 
					 | 
				
			||||||
        for (var i in this.waiting) {
 | 
					 | 
				
			||||||
          let w = this.waiting[i]
 | 
					          let w = this.waiting[i]
 | 
				
			||||||
          // We will just care about w.left
 | 
					          if (Y.utils.compareIds(op.left, w.id)) {
 | 
				
			||||||
          if (Y.utils.compareIds(del.target, w.left)) {
 | 
					            // include the effect of op in w
 | 
				
			||||||
            del.left = newLeft
 | 
					            w.right = op.id
 | 
				
			||||||
 | 
					            // exclude the effect of w in op
 | 
				
			||||||
 | 
					            op.left = w.left
 | 
				
			||||||
 | 
					          } else if (Y.utils.compareIds(op.right, w.id)) {
 | 
				
			||||||
 | 
					            // similar..
 | 
				
			||||||
 | 
					            w.left = op.id
 | 
				
			||||||
 | 
					            op.right = w.right
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      this._tryCallEvents()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					      Call this when you successfully awaited the execution of n Delete operations
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    awaitedDeletes (n, newLeft) {
 | 
				
			||||||
 | 
					      var ops = this.waiting.splice(this.waiting.length - n)
 | 
				
			||||||
 | 
					      for (var j in ops) {
 | 
				
			||||||
 | 
					        var del = ops[j]
 | 
				
			||||||
 | 
					        if (newLeft != null) {
 | 
				
			||||||
 | 
					          for (var i in this.waiting) {
 | 
				
			||||||
 | 
					            let w = this.waiting[i]
 | 
				
			||||||
 | 
					            // We will just care about w.left
 | 
				
			||||||
 | 
					            if (Y.utils.compareIds(del.target, w.left)) {
 | 
				
			||||||
 | 
					              del.left = newLeft
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this._tryCallEvents()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /* (private)
 | 
				
			||||||
 | 
					      Try to execute the events for the waiting operations
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    _tryCallEvents () {
 | 
				
			||||||
 | 
					      this.awaiting--
 | 
				
			||||||
 | 
					      if (this.awaiting <= 0 && this.waiting.length > 0) {
 | 
				
			||||||
 | 
					        var events = this.waiting
 | 
				
			||||||
 | 
					        this.waiting = []
 | 
				
			||||||
 | 
					        this.onevent(events)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this._tryCallEvents()
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  /* (private)
 | 
					  Y.utils.EventHandler = EventHandler
 | 
				
			||||||
    Try to execute the events for the waiting operations
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					    A wrapper for the definition of a custom type.
 | 
				
			||||||
 | 
					    Every custom type must have three properties:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    * createType
 | 
				
			||||||
 | 
					      - Defines the model of a newly created custom type and returns the type
 | 
				
			||||||
 | 
					    * initType
 | 
				
			||||||
 | 
					      - Given a model, creates a custom type
 | 
				
			||||||
 | 
					    * class
 | 
				
			||||||
 | 
					      - the constructor of the custom type (e.g. in order to inherit from a type)
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
  _tryCallEvents () {
 | 
					  class CustomType { // eslint-disable-line
 | 
				
			||||||
    this.awaiting--
 | 
					    constructor (def) {
 | 
				
			||||||
    if (this.awaiting <= 0 && this.waiting.length > 0) {
 | 
					      if (def.createType == null ||
 | 
				
			||||||
      var events = this.waiting
 | 
					        def.initType == null ||
 | 
				
			||||||
      this.waiting = []
 | 
					        def.class == null
 | 
				
			||||||
      this.onevent(events)
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw new Error('Custom type was not initialized correctly!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.createType = def.createType
 | 
				
			||||||
 | 
					      this.initType = def.initType
 | 
				
			||||||
 | 
					      this.class = def.class
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					  Y.utils.CustomType = CustomType
 | 
				
			||||||
Y.utils.EventHandler = EventHandler
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					  /*
 | 
				
			||||||
  A wrapper for the definition of a custom type.
 | 
					    Make a flat copy of an object
 | 
				
			||||||
  Every custom type must have three properties:
 | 
					    (just copy properties)
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
  * createType
 | 
					  function copyObject (o) {
 | 
				
			||||||
    - Defines the model of a newly created custom type and returns the type
 | 
					    var c = {}
 | 
				
			||||||
  * initType
 | 
					    for (var key in o) {
 | 
				
			||||||
    - Given a model, creates a custom type
 | 
					      c[key] = o[key]
 | 
				
			||||||
  * class
 | 
					 | 
				
			||||||
    - the constructor of the custom type (e.g. in order to inherit from a type)
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
class CustomType { // eslint-disable-line
 | 
					 | 
				
			||||||
  constructor (def) {
 | 
					 | 
				
			||||||
    if (def.createType == null ||
 | 
					 | 
				
			||||||
      def.initType == null ||
 | 
					 | 
				
			||||||
      def.class == null
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      throw new Error('Custom type was not initialized correctly!')
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.createType = def.createType
 | 
					    return c
 | 
				
			||||||
    this.initType = def.initType
 | 
					 | 
				
			||||||
    this.class = def.class
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					  Y.utils.copyObject = copyObject
 | 
				
			||||||
Y.utils.CustomType = CustomType
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					  /*
 | 
				
			||||||
  Make a flat copy of an object
 | 
					    Defines a smaller relation on Id's
 | 
				
			||||||
  (just copy properties)
 | 
					  */
 | 
				
			||||||
*/
 | 
					  function smaller (a, b) {
 | 
				
			||||||
function copyObject (o) {
 | 
					    return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
 | 
				
			||||||
  var c = {}
 | 
					 | 
				
			||||||
  for (var key in o) {
 | 
					 | 
				
			||||||
    c[key] = o[key]
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return c
 | 
					  Y.utils.smaller = smaller
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Y.utils.copyObject = copyObject
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					  function compareIds (id1, id2) {
 | 
				
			||||||
  Defines a smaller relation on Id's
 | 
					    if (id1 == null || id2 == null) {
 | 
				
			||||||
*/
 | 
					      if (id1 == null && id2 == null) {
 | 
				
			||||||
function smaller (a, b) {
 | 
					        return true
 | 
				
			||||||
  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
 | 
					      }
 | 
				
			||||||
}
 | 
					      return false
 | 
				
			||||||
Y.utils.smaller = smaller
 | 
					    }
 | 
				
			||||||
 | 
					    if (id1[0] === id2[0] && id1[1] === id2[1]) {
 | 
				
			||||||
function compareIds (id1, id2) {
 | 
					 | 
				
			||||||
  if (id1 == null || id2 == null) {
 | 
					 | 
				
			||||||
    if (id1 == null && id2 == null) {
 | 
					 | 
				
			||||||
      return true
 | 
					      return true
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (id1[0] === id2[0] && id1[1] === id2[1]) {
 | 
					 | 
				
			||||||
    return true
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return false
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  Y.utils.compareIds = compareIds
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
Y.utils.compareIds = compareIds
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										71
									
								
								src/y.js
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								src/y.js
									
									
									
									
									
								
							@ -1,11 +1,68 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* @flow */
 | 
				
			||||||
'use strict'
 | 
					'use strict'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require('./Connector.js')(Y)
 | 
				
			||||||
 | 
					require('./Database.js')(Y)
 | 
				
			||||||
 | 
					require('./Transaction.js')(Y)
 | 
				
			||||||
 | 
					require('./Struct.js')(Y)
 | 
				
			||||||
 | 
					require('./Utils.js')(Y)
 | 
				
			||||||
 | 
					require('./Databases/RedBlackTree.js')(Y)
 | 
				
			||||||
 | 
					require('./Databases/Memory.js')(Y)
 | 
				
			||||||
 | 
					require('./Databases/IndexedDB.js')(Y)
 | 
				
			||||||
 | 
					require('./Connectors/Test.js')(Y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var requiringModules = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = Y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Y.extend = function (name, value) {
 | 
				
			||||||
 | 
					  Y[name] = value
 | 
				
			||||||
 | 
					  var resolves = requiringModules[name]
 | 
				
			||||||
 | 
					  if (requiringModules[name] != null) {
 | 
				
			||||||
 | 
					    for (var i = 0; i < resolves.length; i++) {
 | 
				
			||||||
 | 
					      resolves[i]()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    delete requiringModules[name]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require('./Types/Array.js')(Y)
 | 
				
			||||||
 | 
					require('./Types/Map.js')(Y)
 | 
				
			||||||
 | 
					require('./Types/TextBind.js')(Y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Y (opts) {
 | 
					function Y (opts) {
 | 
				
			||||||
  return new Promise(function (resolve) {
 | 
					  opts.types = opts.types != null ? opts.types : []
 | 
				
			||||||
    var yconfig = new YConfig(opts, function () {
 | 
					  var modules = [opts.db.name, opts.connector.name].concat(opts.types)
 | 
				
			||||||
      yconfig.db.whenUserIdSet(function () {
 | 
					  var promises = []
 | 
				
			||||||
        resolve(yconfig)
 | 
					  for (var i = 0; i < modules.length; i++) {
 | 
				
			||||||
 | 
					    if (Y[modules[i]] == null) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        require(modules[i])(Y)
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        // module does not exist
 | 
				
			||||||
 | 
					        if (window != null) {
 | 
				
			||||||
 | 
					          if (requiringModules[modules[i]] == null) {
 | 
				
			||||||
 | 
					            var imported = document.createElement('script')
 | 
				
			||||||
 | 
					            var name = modules[i].toLowerCase()
 | 
				
			||||||
 | 
					            imported.src = opts.sourceDir + '/y-' + name + '/y-' + name + '.js'
 | 
				
			||||||
 | 
					            document.head.appendChild(imported)
 | 
				
			||||||
 | 
					            requiringModules[modules[i]] = []
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          promises.push(new Promise(function (resolve) {
 | 
				
			||||||
 | 
					            requiringModules[modules[i]].push(resolve)
 | 
				
			||||||
 | 
					          }))
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw e
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return Promise.all(promises).then(function () {
 | 
				
			||||||
 | 
					    return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					      var yconfig = new YConfig(opts, function () {
 | 
				
			||||||
 | 
					        yconfig.db.whenUserIdSet(function () {
 | 
				
			||||||
 | 
					          resolve(yconfig)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
@ -49,9 +106,3 @@ class YConfig {
 | 
				
			|||||||
if (typeof window !== 'undefined') {
 | 
					if (typeof window !== 'undefined') {
 | 
				
			||||||
  window.Y = Y
 | 
					  window.Y = Y
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
if (typeof YConcurrency_TestingMode !== 'undefined') {
 | 
					 | 
				
			||||||
  g.Y = Y //eslint-disable-line
 | 
					 | 
				
			||||||
  // debugger //eslint-disable-line
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
Y.utils = {}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user