switched to *standard* coding style
This commit is contained in:
		
							parent
							
								
									ee116b8ca4
								
							
						
					
					
						commit
						ee983ceff6
					
				@ -1,4 +0,0 @@
 | 
				
			|||||||
build/
 | 
					 | 
				
			||||||
y.js
 | 
					 | 
				
			||||||
y.js.map
 | 
					 | 
				
			||||||
interfaces/
 | 
					 | 
				
			||||||
							
								
								
									
										43
									
								
								.eslintrc
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								.eslintrc
									
									
									
									
									
								
							@ -1,43 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "env": {
 | 
					 | 
				
			||||||
    "es6": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "rules": {
 | 
					 | 
				
			||||||
    "strict": 0,
 | 
					 | 
				
			||||||
    "camelcase": [1, {"properties": "never"}],
 | 
					 | 
				
			||||||
    "no-underscore-dangle": 0,
 | 
					 | 
				
			||||||
    "no-constant-condition": 0,
 | 
					 | 
				
			||||||
    "no-empty": 0,
 | 
					 | 
				
			||||||
    "new-cap": [2, { "capIsNewExceptions": ["List", "Y"] }],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "parser": "babel-eslint",
 | 
					 | 
				
			||||||
  "globals": {
 | 
					 | 
				
			||||||
    "copyObject": true,
 | 
					 | 
				
			||||||
    "Struct": true,
 | 
					 | 
				
			||||||
    "OperationStore": true,
 | 
					 | 
				
			||||||
    "AbstractOperationStore": true,
 | 
					 | 
				
			||||||
    "AbstractTransaction": true,
 | 
					 | 
				
			||||||
    "AbstractConnector": true,
 | 
					 | 
				
			||||||
    "Transaction": true,
 | 
					 | 
				
			||||||
    "IndexedDB": true,
 | 
					 | 
				
			||||||
    "IDBRequest": true,
 | 
					 | 
				
			||||||
    "GeneratorFunction": true,
 | 
					 | 
				
			||||||
    "Y": true,
 | 
					 | 
				
			||||||
    "setTimeout": true,
 | 
					 | 
				
			||||||
    "setInterval": true,
 | 
					 | 
				
			||||||
    "Operation": true,
 | 
					 | 
				
			||||||
    "getRandom": true,
 | 
					 | 
				
			||||||
    "RBTree": true,
 | 
					 | 
				
			||||||
    "compareIds": true,
 | 
					 | 
				
			||||||
    "EventHandler": true,
 | 
					 | 
				
			||||||
    "compareAllUsers": true,
 | 
					 | 
				
			||||||
    "createUsers": true,
 | 
					 | 
				
			||||||
    "getRandomNumber": true,
 | 
					 | 
				
			||||||
    "applyRandomTransactions": true,
 | 
					 | 
				
			||||||
    "CustomType": true,
 | 
					 | 
				
			||||||
    "window": true,
 | 
					 | 
				
			||||||
    "document": true,
 | 
					 | 
				
			||||||
    "smaller": true,
 | 
					 | 
				
			||||||
    "wait": true
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										13
									
								
								.flowconfig
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.flowconfig
									
									
									
									
									
								
							@ -1,13 +0,0 @@
 | 
				
			|||||||
[ignore]
 | 
					 | 
				
			||||||
.*/node_modules/.*
 | 
					 | 
				
			||||||
.*/build/.*
 | 
					 | 
				
			||||||
./y.js
 | 
					 | 
				
			||||||
./y.js.map
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[include]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[libs]
 | 
					 | 
				
			||||||
./interfaces
 | 
					 | 
				
			||||||
./src
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[options]
 | 
					 | 
				
			||||||
@ -1,29 +1,30 @@
 | 
				
			|||||||
 | 
					/* global Y */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y({
 | 
					Y({
 | 
				
			||||||
  db: {
 | 
					  db: {
 | 
				
			||||||
    name: "Memory"
 | 
					    name: 'Memory'
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  connector: {
 | 
					  connector: {
 | 
				
			||||||
    name: "WebRTC",
 | 
					    name: 'WebRTC',
 | 
				
			||||||
    room: "mineeeeeee",
 | 
					    room: 'mineeeeeee',
 | 
				
			||||||
    debug: true
 | 
					    debug: true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}).then(function(yconfig){
 | 
					}).then(function (yconfig) {
 | 
				
			||||||
  window.y = yconfig.root;
 | 
					  window.y = yconfig.root
 | 
				
			||||||
  window.yconfig = yconfig;
 | 
					  window.yconfig = yconfig
 | 
				
			||||||
  var textarea = document.getElementById("textfield");
 | 
					  var textarea = document.getElementById('textfield')
 | 
				
			||||||
  var contenteditable = document.getElementById("contenteditable");
 | 
					  var contenteditable = document.getElementById('contenteditable')
 | 
				
			||||||
  yconfig.root.observe(function(events){
 | 
					  yconfig.root.observe(function (events) {
 | 
				
			||||||
    for (var e in events) {
 | 
					    for (var e in events) {
 | 
				
			||||||
      var event = events[e];
 | 
					      var event = events[e]
 | 
				
			||||||
      if (event.name === "text" && (event.type === "add" || event.type === "update")) {
 | 
					      if (event.name === 'text' && (event.type === 'add' || event.type === 'update')) {
 | 
				
			||||||
        event.object.get(event.name).then(function(text){ //eslint-disable-line
 | 
					        event.object.get(event.name).then(function (text) { // eslint-disable-line
 | 
				
			||||||
          text.bind(textarea);
 | 
					          text.bind(textarea)
 | 
				
			||||||
          text.bind(contenteditable);
 | 
					          text.bind(contenteditable)
 | 
				
			||||||
          window.ytext = text;
 | 
					          window.ytext = text
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  yconfig.root.set("text", Y.TextBind);
 | 
					  yconfig.root.set('text', Y.TextBind)
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										124
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								gulpfile.js
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
/*eslint-env node */
 | 
					/* eslint-env node */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Gulp Commands
 | 
					/** Gulp Commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,47 +38,42 @@
 | 
				
			|||||||
        Builds the test suite
 | 
					        Builds the test suite
 | 
				
			||||||
    - test:
 | 
					    - test:
 | 
				
			||||||
        Test this library
 | 
					        Test this library
 | 
				
			||||||
    - lint:
 | 
					 | 
				
			||||||
        Lint this library. A successful lint is required for committing to this repository!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var gulp = require('gulp')
 | 
				
			||||||
var gulp = require("gulp");
 | 
					var sourcemaps = require('gulp-sourcemaps')
 | 
				
			||||||
var sourcemaps = require("gulp-sourcemaps");
 | 
					var babel = require('gulp-babel')
 | 
				
			||||||
var babel = require("gulp-babel");
 | 
					var uglify = require('gulp-uglify')
 | 
				
			||||||
var uglify = require("gulp-uglify");
 | 
					var minimist = require('minimist')
 | 
				
			||||||
var minimist = require("minimist");
 | 
					var jasmine = require('gulp-jasmine')
 | 
				
			||||||
var eslint = require("gulp-eslint");
 | 
					var jasmineBrowser = require('gulp-jasmine-browser')
 | 
				
			||||||
var jasmine = require("gulp-jasmine");
 | 
					var concat = require('gulp-concat')
 | 
				
			||||||
var jasmineBrowser = require("gulp-jasmine-browser");
 | 
					var watch = require('gulp-watch')
 | 
				
			||||||
var concat = require("gulp-concat");
 | 
					 | 
				
			||||||
var watch = require("gulp-watch");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
var polyfills = [
 | 
					var polyfills = [
 | 
				
			||||||
  "./node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js"
 | 
					  './node_modules/gulp-babel/node_modules/babel-core/node_modules/regenerator/runtime.js'
 | 
				
			||||||
];
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var options = minimist(process.argv.slice(2), {
 | 
					var options = minimist(process.argv.slice(2), {
 | 
				
			||||||
  string: ["export", "name", "testport", "testfiles"],
 | 
					  string: ['export', 'name', 'testport', 'testfiles'],
 | 
				
			||||||
  default: {
 | 
					  default: {
 | 
				
			||||||
    export: "ignore",
 | 
					    export: 'ignore',
 | 
				
			||||||
    name: "y.js",
 | 
					    name: 'y.js',
 | 
				
			||||||
    testport: "8888",
 | 
					    testport: '8888',
 | 
				
			||||||
    testfiles: "src/**/*.js"
 | 
					    testfiles: 'src/**/*.js'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var files = {
 | 
					var files = {
 | 
				
			||||||
  y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/Utils.js",
 | 
					  y: polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
 | 
				
			||||||
    "src/OperationStores/RedBlackTree.js", "src/**/*.js", "!src/**/*.spec.js"]),
 | 
					    'src/OperationStores/RedBlackTree.js', 'src/**/*.js', '!src/**/*.spec.js']),
 | 
				
			||||||
  lint: ["src/**/*.js", "gulpfile.js"],
 | 
					 | 
				
			||||||
  test: polyfills.concat([options.testfiles]),
 | 
					  test: polyfills.concat([options.testfiles]),
 | 
				
			||||||
  build_test: ["build_test/y.js"]
 | 
					  build_test: ['build_test/y.js']
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task("build", function () {
 | 
					gulp.task('build', function () {
 | 
				
			||||||
  /*return gulp.src(files.y)
 | 
					  /*
 | 
				
			||||||
 | 
					    return gulp.src(files.y)
 | 
				
			||||||
    .pipe(sourcemaps.init())
 | 
					    .pipe(sourcemaps.init())
 | 
				
			||||||
    .pipe(concat(options.name))
 | 
					    .pipe(concat(options.name))
 | 
				
			||||||
    .pipe(babel({
 | 
					    .pipe(babel({
 | 
				
			||||||
@ -93,68 +88,59 @@ gulp.task("build", function () {
 | 
				
			|||||||
    .pipe(sourcemaps.init())
 | 
					    .pipe(sourcemaps.init())
 | 
				
			||||||
    .pipe(concat(options.name))
 | 
					    .pipe(concat(options.name))
 | 
				
			||||||
    .pipe(babel({
 | 
					    .pipe(babel({
 | 
				
			||||||
       loose: "all",
 | 
					      loose: 'all',
 | 
				
			||||||
       modules: "ignore",
 | 
					      modules: 'ignore',
 | 
				
			||||||
       optional: ["es7.asyncFunctions"],
 | 
					      optional: ['es7.asyncFunctions'],
 | 
				
			||||||
       blacklist: ["regenerator"],
 | 
					      blacklist: ['regenerator'],
 | 
				
			||||||
      experimental: true
 | 
					      experimental: true
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
    .pipe(sourcemaps.write())
 | 
					    .pipe(sourcemaps.write())
 | 
				
			||||||
     .pipe(gulp.dest("."));
 | 
					    .pipe(gulp.dest('.'))
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task("lint", function(){
 | 
					gulp.task('test', function () {
 | 
				
			||||||
  return gulp.src(files.lint)
 | 
					 | 
				
			||||||
    .pipe(eslint())
 | 
					 | 
				
			||||||
    .pipe(eslint.format())
 | 
					 | 
				
			||||||
    .pipe(eslint.failOnError());
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gulp.task("test", function () {
 | 
					 | 
				
			||||||
  return gulp.src(files.test)
 | 
					  return gulp.src(files.test)
 | 
				
			||||||
    .pipe(sourcemaps.init())
 | 
					    .pipe(sourcemaps.init())
 | 
				
			||||||
    .pipe(concat("jasmine"))
 | 
					    .pipe(concat('jasmine'))
 | 
				
			||||||
    .pipe(babel({
 | 
					    .pipe(babel({
 | 
				
			||||||
      loose: "all",
 | 
					      loose: 'all',
 | 
				
			||||||
      optional: ["es7.asyncFunctions"],
 | 
					      optional: ['es7.asyncFunctions'],
 | 
				
			||||||
      modules: "ignore",
 | 
					      modules: 'ignore',
 | 
				
			||||||
      experimental: true
 | 
					      experimental: true
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
    .pipe(uglify())
 | 
					    .pipe(uglify())
 | 
				
			||||||
    .pipe(sourcemaps.write())
 | 
					    .pipe(sourcemaps.write())
 | 
				
			||||||
    .pipe(gulp.dest("build"))
 | 
					    .pipe(gulp.dest('build'))
 | 
				
			||||||
    .pipe(jasmine({
 | 
					    .pipe(jasmine({
 | 
				
			||||||
      verbose: true,
 | 
					      verbose: true,
 | 
				
			||||||
      includeStuckTrace: true
 | 
					      includeStuckTrace: true
 | 
				
			||||||
    }));
 | 
					    }))
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task("build_jasmine_browser", function(){
 | 
					gulp.task('build_jasmine_browser', function () {
 | 
				
			||||||
  gulp.src(files.test)
 | 
					  gulp.src(files.test)
 | 
				
			||||||
    .pipe(sourcemaps.init())
 | 
					    .pipe(sourcemaps.init())
 | 
				
			||||||
   .pipe(concat("jasmine_browser.js"))
 | 
					    .pipe(concat('jasmine_browser.js'))
 | 
				
			||||||
    .pipe(babel({
 | 
					    .pipe(babel({
 | 
				
			||||||
     loose: "all",
 | 
					      loose: 'all',
 | 
				
			||||||
     modules: "ignore",
 | 
					      modules: 'ignore',
 | 
				
			||||||
     optional: ["es7.asyncFunctions"],
 | 
					      optional: ['es7.asyncFunctions'],
 | 
				
			||||||
      // blacklist: "regenerator",
 | 
					      // blacklist: "regenerator",
 | 
				
			||||||
      experimental: true
 | 
					      experimental: true
 | 
				
			||||||
    }))
 | 
					    }))
 | 
				
			||||||
    .pipe(sourcemaps.write())
 | 
					    .pipe(sourcemaps.write())
 | 
				
			||||||
   .pipe(gulp.dest("build"));
 | 
					    .pipe(gulp.dest('build'))
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('develop', ['build_jasmine_browser', 'build'], function () {
 | 
				
			||||||
 | 
					  gulp.watch(files.test, ['build_jasmine_browser'])
 | 
				
			||||||
 | 
					  // gulp.watch(files.test, ["test"])
 | 
				
			||||||
 | 
					  gulp.watch(files.test, ['build'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task("develop", ["build_jasmine_browser", "build"], function(){
 | 
					  return gulp.src('build/jasmine_browser.js')
 | 
				
			||||||
 | 
					    .pipe(watch('build/jasmine_browser.js'))
 | 
				
			||||||
  gulp.watch(files.test, ["build_jasmine_browser"]);
 | 
					 | 
				
			||||||
  //gulp.watch(files.test, ["test"]);
 | 
					 | 
				
			||||||
  gulp.watch(files.test, ["build"]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return gulp.src("build/jasmine_browser.js")
 | 
					 | 
				
			||||||
    .pipe(watch("build/jasmine_browser.js"))
 | 
					 | 
				
			||||||
    .pipe(jasmineBrowser.specRunner())
 | 
					    .pipe(jasmineBrowser.specRunner())
 | 
				
			||||||
    .pipe(jasmineBrowser.server({port: options.testport}));
 | 
					    .pipe(jasmineBrowser.server({port: options.testport}))
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gulp.task("default", ["build", "test"]);
 | 
					gulp.task('default', ['build', 'test'])
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
/* @flow */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare var describe : Function;
 | 
					 | 
				
			||||||
declare var it : Function;
 | 
					 | 
				
			||||||
declare var expect : Function;
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
								
							@ -5,13 +5,21 @@
 | 
				
			|||||||
  "main": "y.js",
 | 
					  "main": "y.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "test": "gulp test",
 | 
					    "test": "gulp test",
 | 
				
			||||||
    "lint": "gulp lint",
 | 
					    "lint": "standard",
 | 
				
			||||||
    "build": "gulp build"
 | 
					    "build": "gulp build"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "pre-commit": [
 | 
					  "pre-commit": [
 | 
				
			||||||
    "lint",
 | 
					    "lint",
 | 
				
			||||||
    "build"
 | 
					    "build"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
					  "standard": {
 | 
				
			||||||
 | 
					    "parser": "babel-eslint",
 | 
				
			||||||
 | 
					    "ignore": [
 | 
				
			||||||
 | 
					      "build/**",
 | 
				
			||||||
 | 
					      "./y.js",
 | 
				
			||||||
 | 
					      "./y.js.map"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					    "type": "git",
 | 
				
			||||||
    "url": "https://github.com/y-js/yjs.git"
 | 
					    "url": "https://github.com/y-js/yjs.git"
 | 
				
			||||||
@ -33,12 +41,10 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "homepage": "http://y-js.org",
 | 
					  "homepage": "http://y-js.org",
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "babel-eslint": "^3.1.15",
 | 
					    "babel-eslint": "^3.1.23",
 | 
				
			||||||
    "eslint": "^0.22.1",
 | 
					 | 
				
			||||||
    "gulp": "^3.9.0",
 | 
					    "gulp": "^3.9.0",
 | 
				
			||||||
    "gulp-babel": "^5.1.0",
 | 
					    "gulp-babel": "^5.1.0",
 | 
				
			||||||
    "gulp-concat": "^2.5.2",
 | 
					    "gulp-concat": "^2.5.2",
 | 
				
			||||||
    "gulp-eslint": "^0.13.2",
 | 
					 | 
				
			||||||
    "gulp-jasmine": "^2.0.1",
 | 
					    "gulp-jasmine": "^2.0.1",
 | 
				
			||||||
    "gulp-jasmine-browser": "^0.1.3",
 | 
					    "gulp-jasmine-browser": "^0.1.3",
 | 
				
			||||||
    "gulp-sourcemaps": "^1.5.2",
 | 
					    "gulp-sourcemaps": "^1.5.2",
 | 
				
			||||||
@ -46,6 +52,7 @@
 | 
				
			|||||||
    "gulp-util": "^3.0.5",
 | 
					    "gulp-util": "^3.0.5",
 | 
				
			||||||
    "gulp-watch": "^4.2.4",
 | 
					    "gulp-watch": "^4.2.4",
 | 
				
			||||||
    "minimist": "^1.1.1",
 | 
					    "minimist": "^1.1.1",
 | 
				
			||||||
    "pre-commit": "^1.0.10"
 | 
					    "pre-commit": "^1.0.10",
 | 
				
			||||||
 | 
					    "standard": "^5.0.0-2"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										247
									
								
								src/Connector.js
									
									
									
									
									
								
							
							
						
						
									
										247
									
								
								src/Connector.js
									
									
									
									
									
								
							@ -1,192 +1,191 @@
 | 
				
			|||||||
 | 
					class AbstractConnector { // eslint-disable-line no-unused-vars
 | 
				
			||||||
class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
					 | 
				
			||||||
  /*
 | 
					  /*
 | 
				
			||||||
    opts
 | 
					    opts
 | 
				
			||||||
     .role : String Role of this client ("master" or "slave")
 | 
					     .role : String Role of this client ("master" or "slave")
 | 
				
			||||||
     .userId : String that uniquely defines the user.
 | 
					     .userId : String that uniquely defines the user.
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
  constructor (y, opts) {
 | 
					  constructor (y, opts) {
 | 
				
			||||||
    this.y = y;
 | 
					    this.y = y
 | 
				
			||||||
    if (opts == null){
 | 
					    if (opts == null) {
 | 
				
			||||||
      opts = {};
 | 
					      opts = {}
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (opts.role == null || opts.role === "master") {
 | 
					    if (opts.role == null || opts.role === 'master') {
 | 
				
			||||||
      this.role = "master";
 | 
					      this.role = 'master'
 | 
				
			||||||
    } else if (opts.role === "slave") {
 | 
					    } else if (opts.role === 'slave') {
 | 
				
			||||||
      this.role = "slave";
 | 
					      this.role = 'slave'
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw new Error("Role must be either 'master' or 'slave'!");
 | 
					      throw new Error("Role must be either 'master' or 'slave'!")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.role = opts.role;
 | 
					    this.role = opts.role
 | 
				
			||||||
    this.connections = {};
 | 
					    this.connections = {}
 | 
				
			||||||
    this.userEventListeners = [];
 | 
					    this.userEventListeners = []
 | 
				
			||||||
    this.whenSyncedListeners = [];
 | 
					    this.whenSyncedListeners = []
 | 
				
			||||||
    this.currentSyncTarget = null;
 | 
					    this.currentSyncTarget = null
 | 
				
			||||||
    this.syncingClients = [];
 | 
					    this.syncingClients = []
 | 
				
			||||||
    this.forwardToSyncingClients = (opts.forwardToSyncingClients === false) ? false : true;
 | 
					    this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
 | 
				
			||||||
    this.debug = opts.debug ? true : false;
 | 
					    this.debug = opts.debug === true
 | 
				
			||||||
    this.broadcastedHB = false;
 | 
					    this.broadcastedHB = false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  setUserId (userId) {
 | 
					  setUserId (userId) {
 | 
				
			||||||
    this.userId = userId;
 | 
					    this.userId = userId
 | 
				
			||||||
    this.y.db.setUserId(userId);
 | 
					    this.y.db.setUserId(userId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  onUserEvent (f) {
 | 
					  onUserEvent (f) {
 | 
				
			||||||
    this.userEventListeners.push(f);
 | 
					    this.userEventListeners.push(f)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  userLeft (user : string) {
 | 
					  userLeft (user) {
 | 
				
			||||||
    delete this.connections[user];
 | 
					    delete this.connections[user]
 | 
				
			||||||
    if (user === this.currentSyncTarget){
 | 
					    if (user === this.currentSyncTarget) {
 | 
				
			||||||
      this.currentSyncTarget = null;
 | 
					      this.currentSyncTarget = null
 | 
				
			||||||
      this.findNextSyncTarget();
 | 
					      this.findNextSyncTarget()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (var f of this.userEventListeners){
 | 
					    for (var f of this.userEventListeners) {
 | 
				
			||||||
      f({
 | 
					      f({
 | 
				
			||||||
        action: "userLeft",
 | 
					        action: 'userLeft',
 | 
				
			||||||
        user: user
 | 
					        user: user
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  userJoined (user, role) {
 | 
					  userJoined (user, role) {
 | 
				
			||||||
    if(role == null){
 | 
					    if (role == null) {
 | 
				
			||||||
      throw new Error("You must specify the role of the joined user!");
 | 
					      throw new Error('You must specify the role of the joined user!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.connections[user] != null) {
 | 
					    if (this.connections[user] != null) {
 | 
				
			||||||
      throw new Error("This user already joined!");
 | 
					      throw new Error('This user already joined!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.connections[user] = {
 | 
					    this.connections[user] = {
 | 
				
			||||||
      isSynced: false,
 | 
					      isSynced: false,
 | 
				
			||||||
      role: role
 | 
					      role: role
 | 
				
			||||||
    };
 | 
					    }
 | 
				
			||||||
    for (var f of this.userEventListeners) {
 | 
					    for (var f of this.userEventListeners) {
 | 
				
			||||||
      f({
 | 
					      f({
 | 
				
			||||||
        action: "userJoined",
 | 
					        action: 'userJoined',
 | 
				
			||||||
        user: user,
 | 
					        user: user,
 | 
				
			||||||
        role: role
 | 
					        role: role
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.currentSyncTarget == null) {
 | 
					    if (this.currentSyncTarget == null) {
 | 
				
			||||||
      this.findNextSyncTarget();
 | 
					      this.findNextSyncTarget()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // Execute a function _when_ we are connected.
 | 
					  // Execute a function _when_ we are connected.
 | 
				
			||||||
  // If not connected, wait until connected
 | 
					  // If not connected, wait until connected
 | 
				
			||||||
  whenSynced (f) {
 | 
					  whenSynced (f) {
 | 
				
			||||||
    if (this.isSynced === true) {
 | 
					    if (this.isSynced === true) {
 | 
				
			||||||
      f();
 | 
					      f()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.whenSyncedListeners.push(f);
 | 
					      this.whenSyncedListeners.push(f)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // returns false, if there is no sync target
 | 
					  // returns false, if there is no sync target
 | 
				
			||||||
  // true otherwise
 | 
					  // true otherwise
 | 
				
			||||||
  findNextSyncTarget () {
 | 
					  findNextSyncTarget () {
 | 
				
			||||||
    if (this.currentSyncTarget != null) {
 | 
					    if (this.currentSyncTarget != null) {
 | 
				
			||||||
      return; // "The current sync has not finished!"
 | 
					      return // "The current sync has not finished!"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var syncUser = null;
 | 
					    var syncUser = null
 | 
				
			||||||
    for (var uid in this.connections) {
 | 
					    for (var uid in this.connections) {
 | 
				
			||||||
      if (!this.connections[uid].isSynced) {
 | 
					      if (!this.connections[uid].isSynced) {
 | 
				
			||||||
        syncUser = uid;
 | 
					        syncUser = uid
 | 
				
			||||||
        break;
 | 
					        break
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (syncUser != null){
 | 
					    if (syncUser != null) {
 | 
				
			||||||
      var conn = this;
 | 
					      var conn = this
 | 
				
			||||||
      this.currentSyncTarget = syncUser;
 | 
					      this.currentSyncTarget = syncUser
 | 
				
			||||||
      this.y.db.requestTransaction(function*(){
 | 
					      this.y.db.requestTransaction(function *() {
 | 
				
			||||||
        conn.send(syncUser, {
 | 
					        conn.send(syncUser, {
 | 
				
			||||||
          type: "sync step 1",
 | 
					          type: 'sync step 1',
 | 
				
			||||||
          stateVector: yield* this.getStateVector()
 | 
					          stateVector: yield* this.getStateVector()
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // set the state to synced!
 | 
					    // set the state to synced!
 | 
				
			||||||
    if (!this.isSynced) {
 | 
					    if (!this.isSynced) {
 | 
				
			||||||
      this.isSynced = true;
 | 
					      this.isSynced = true
 | 
				
			||||||
      for (var f of this.whenSyncedListeners) {
 | 
					      for (var f of this.whenSyncedListeners) {
 | 
				
			||||||
        f();
 | 
					        f()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.whenSyncedListeners = null;
 | 
					      this.whenSyncedListeners = null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  send (uid, message) {
 | 
					  send (uid, message) {
 | 
				
			||||||
    if (this.debug) {
 | 
					    if (this.debug) {
 | 
				
			||||||
      console.log(`me -> ${uid}: ${message.type}`);//eslint-disable-line
 | 
					      console.log(`me -> ${uid}: ${message.type}`);// eslint-disable-line
 | 
				
			||||||
      console.dir(m); //eslint-disable-line
 | 
					      console.dir(m); // eslint-disable-line
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    super(uid, message);
 | 
					    super(uid, message)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // You received a raw message, and you know that it is intended for to Yjs. Then call this function.
 | 
					  // You received a raw message, and you know that it is intended for to Yjs. Then call this function.
 | 
				
			||||||
  receiveMessage (sender, m){
 | 
					  receiveMessage (sender, m) {
 | 
				
			||||||
    if (sender === this.userId) {
 | 
					    if (sender === this.userId) {
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.debug) {
 | 
					    if (this.debug) {
 | 
				
			||||||
      console.log(`${sender} -> me: ${m.type}`);//eslint-disable-line
 | 
					      console.log(`${sender} -> me: ${m.type}`);// eslint-disable-line
 | 
				
			||||||
      console.dir(m); //eslint-disable-line
 | 
					      console.dir(m); // eslint-disable-line
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (m.type === "sync step 1") {
 | 
					    if (m.type === 'sync step 1') {
 | 
				
			||||||
      // TODO: make transaction, stream the ops
 | 
					      // TODO: make transaction, stream the ops
 | 
				
			||||||
      let conn = this;
 | 
					      let conn = this
 | 
				
			||||||
      this.y.db.requestTransaction(function*(){
 | 
					      this.y.db.requestTransaction(function *() {
 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateVector);
 | 
					        var ops = yield* this.getOperations(m.stateVector)
 | 
				
			||||||
        var sv = yield* this.getStateVector();
 | 
					        var sv = yield* this.getStateVector()
 | 
				
			||||||
        conn.send(sender, {
 | 
					        conn.send(sender, {
 | 
				
			||||||
          type: "sync step 2",
 | 
					          type: 'sync step 2',
 | 
				
			||||||
          os: ops,
 | 
					          os: ops,
 | 
				
			||||||
          stateVector: sv
 | 
					          stateVector: sv
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        if (this.forwardToSyncingClients) {
 | 
					        if (this.forwardToSyncingClients) {
 | 
				
			||||||
          conn.syncingClients.push(sender);
 | 
					          conn.syncingClients.push(sender)
 | 
				
			||||||
          setTimeout(function(){
 | 
					          setTimeout(function () {
 | 
				
			||||||
            conn.syncingClients = conn.syncingClients.filter(function(cli){
 | 
					            conn.syncingClients = conn.syncingClients.filter(function (cli) {
 | 
				
			||||||
              return cli !== sender;
 | 
					              return cli !== sender
 | 
				
			||||||
            });
 | 
					            })
 | 
				
			||||||
            conn.send(sender, {
 | 
					            conn.send(sender, {
 | 
				
			||||||
              type: "sync done"
 | 
					              type: 'sync done'
 | 
				
			||||||
            });
 | 
					            })
 | 
				
			||||||
          }, conn.syncingClientDuration);
 | 
					          }, conn.syncingClientDuration)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          conn.send(sender, {
 | 
					          conn.send(sender, {
 | 
				
			||||||
            type: "sync done"
 | 
					            type: 'sync done'
 | 
				
			||||||
          });
 | 
					          })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    } else if (m.type === "sync step 2") {
 | 
					    } else if (m.type === 'sync step 2') {
 | 
				
			||||||
      this.y.db.apply(m.os);
 | 
					      this.y.db.apply(m.os)
 | 
				
			||||||
      let conn = this;
 | 
					      let conn = this
 | 
				
			||||||
      var broadcastHB = !this.broadcastedHB;
 | 
					      var broadcastHB = !this.broadcastedHB
 | 
				
			||||||
      this.broadcastedHB = true;
 | 
					      this.broadcastedHB = true
 | 
				
			||||||
      this.y.db.requestTransaction(function*(){
 | 
					      this.y.db.requestTransaction(function *() {
 | 
				
			||||||
        var ops = yield* this.getOperations(m.stateVector);
 | 
					        var ops = yield* this.getOperations(m.stateVector)
 | 
				
			||||||
        if (ops.length > 0) {
 | 
					        if (ops.length > 0) {
 | 
				
			||||||
          m = {
 | 
					          m = {
 | 
				
			||||||
            type: "update",
 | 
					            type: 'update',
 | 
				
			||||||
            ops: ops
 | 
					            ops: ops
 | 
				
			||||||
          };
 | 
					          }
 | 
				
			||||||
          if (!broadcastHB) {
 | 
					          if (!broadcastHB) {
 | 
				
			||||||
            conn.send(sender, m);
 | 
					            conn.send(sender, m)
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            // broadcast only once!
 | 
					            // broadcast only once!
 | 
				
			||||||
            conn.broadcast(m);
 | 
					            conn.broadcast(m)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    } else if (m.type === "sync done") {
 | 
					    } else if (m.type === 'sync done') {
 | 
				
			||||||
      this.connections[sender].isSynced = true;
 | 
					      this.connections[sender].isSynced = true
 | 
				
			||||||
      if (sender === this.currentSyncTarget) {
 | 
					      if (sender === this.currentSyncTarget) {
 | 
				
			||||||
        this.currentSyncTarget = null;
 | 
					        this.currentSyncTarget = null
 | 
				
			||||||
        this.findNextSyncTarget();
 | 
					        this.findNextSyncTarget()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } 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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // Currently, the HB encodes operations as JSON. For the moment I want to keep it
 | 
					  // Currently, the HB encodes operations as JSON. For the moment I want to keep it
 | 
				
			||||||
@ -202,36 +201,36 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
  // 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) {
 | 
					    function parseObject (node) {
 | 
				
			||||||
      var json = {};
 | 
					      var json = {}
 | 
				
			||||||
      for (var attrName in node.attrs) {
 | 
					      for (var attrName in node.attrs) {
 | 
				
			||||||
        var value = node.attrs[attrName];
 | 
					        var value = node.attrs[attrName]
 | 
				
			||||||
        var int = parseInt(value);
 | 
					        var int = parseInt(value, 10)
 | 
				
			||||||
        if (isNaN(int) || ("" + int) !== value){
 | 
					        if (isNaN(int) || ('' + int) !== value) {
 | 
				
			||||||
          json[attrName] = value;
 | 
					          json[attrName] = value
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          json[attrName] = int;
 | 
					          json[attrName] = int
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      for (var n in node.children){
 | 
					      for (var n in node.children) {
 | 
				
			||||||
        var name = n.name;
 | 
					        var name = n.name
 | 
				
			||||||
        if (n.getAttribute("isArray") === "true") {
 | 
					        if (n.getAttribute('isArray') === 'true') {
 | 
				
			||||||
          json[name] = parseArray(n);
 | 
					          json[name] = parseArray(n)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          json[name] = parseObject(n);
 | 
					          json[name] = parseObject(n)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return json;
 | 
					      return json
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    parseObject(m);
 | 
					    parseObject(m)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // encode message in xml
 | 
					  // encode message in xml
 | 
				
			||||||
  // we use string because Strophe only accepts an "xml-string"..
 | 
					  // we use string because Strophe only accepts an "xml-string"..
 | 
				
			||||||
@ -245,34 +244,34 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    // attributes is optional
 | 
					    // attributes is optional
 | 
				
			||||||
    function encodeObject (m, json) {
 | 
					    function encodeObject (m, json) {
 | 
				
			||||||
      for (var name in json) {
 | 
					      for (var name in json) {
 | 
				
			||||||
        var value = json[name];
 | 
					        var value = json[name]
 | 
				
			||||||
        if (name == null) {
 | 
					        if (name == null) {
 | 
				
			||||||
          // nop
 | 
					          // nop
 | 
				
			||||||
        } else if (value.constructor === Object) {
 | 
					        } else if (value.constructor === Object) {
 | 
				
			||||||
          encodeObject(m.c(name), value);
 | 
					          encodeObject(m.c(name), value)
 | 
				
			||||||
        } else if (value.constructor === Array) {
 | 
					        } else if (value.constructor === Array) {
 | 
				
			||||||
          encodeArray(m.c(name), value);
 | 
					          encodeArray(m.c(name), value)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          m.setAttribute(name, value);
 | 
					          m.setAttribute(name, value)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    function encodeArray (m, array) {
 | 
					    function encodeArray (m, array) {
 | 
				
			||||||
      m.setAttribute("isArray", "true");
 | 
					      m.setAttribute('isArray', 'true')
 | 
				
			||||||
      for (var e of array) {
 | 
					      for (var e of array) {
 | 
				
			||||||
        if (e.constructor === Object) {
 | 
					        if (e.constructor === Object) {
 | 
				
			||||||
          encodeObject(m.c("array-element"), e);
 | 
					          encodeObject(m.c('array-element'), e)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          encodeArray(m.c("array-element"), e);
 | 
					          encodeArray(m.c('array-element'), e)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (obj.constructor === Object) {
 | 
					    if (obj.constructor === Object) {
 | 
				
			||||||
      encodeObject(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
 | 
					      encodeObject(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
				
			||||||
    } else if (obj.constructor === Array) {
 | 
					    } else if (obj.constructor === Array) {
 | 
				
			||||||
      encodeArray(msg.c("y", { xmlns: "http://y.ninja/connector-stanza" }), obj);
 | 
					      encodeArray(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw new Error("I can't encode this json!");
 | 
					      throw new Error("I can't encode this json!")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,102 +1,104 @@
 | 
				
			|||||||
 | 
					/* global getRandom, AbstractConnector, Y, wait */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var globalRoom = {
 | 
					var globalRoom = {
 | 
				
			||||||
  users: {},
 | 
					  users: {},
 | 
				
			||||||
  buffers: {},
 | 
					  buffers: {},
 | 
				
			||||||
  removeUser: function(user : AbstractConnector){
 | 
					  removeUser: function (user) {
 | 
				
			||||||
    for (var i in this.users) {
 | 
					    for (var i in this.users) {
 | 
				
			||||||
      this.users[i].userLeft(user);
 | 
					      this.users[i].userLeft(user)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    delete this.users[user];
 | 
					    delete this.users[user]
 | 
				
			||||||
    delete this.buffers[user];
 | 
					    delete this.buffers[user]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  addUser: function(connector){
 | 
					  addUser: function (connector) {
 | 
				
			||||||
    this.users[connector.userId] = connector;
 | 
					    this.users[connector.userId] = connector
 | 
				
			||||||
    this.buffers[connector.userId] = [];
 | 
					    this.buffers[connector.userId] = []
 | 
				
			||||||
    for (var uname in this.users) {
 | 
					    for (var uname in this.users) {
 | 
				
			||||||
      if (uname !== connector.userId) {
 | 
					      if (uname !== connector.userId) {
 | 
				
			||||||
        var u = this.users[uname];
 | 
					        var u = this.users[uname]
 | 
				
			||||||
        u.userJoined(connector.userId, "master");
 | 
					        u.userJoined(connector.userId, 'master')
 | 
				
			||||||
        connector.userJoined(u.userId, "master");
 | 
					        connector.userJoined(u.userId, 'master')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
function flushOne(){
 | 
					function flushOne () {
 | 
				
			||||||
  var bufs = [];
 | 
					  var bufs = []
 | 
				
			||||||
  for (var i in globalRoom.buffers) {
 | 
					  for (var i in globalRoom.buffers) {
 | 
				
			||||||
    if (globalRoom.buffers[i].length > 0) {
 | 
					    if (globalRoom.buffers[i].length > 0) {
 | 
				
			||||||
      bufs.push(i);
 | 
					      bufs.push(i)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (bufs.length > 0) {
 | 
					  if (bufs.length > 0) {
 | 
				
			||||||
    var userId = getRandom(bufs);
 | 
					    var userId = getRandom(bufs)
 | 
				
			||||||
    var m = globalRoom.buffers[userId].shift();
 | 
					    var m = globalRoom.buffers[userId].shift()
 | 
				
			||||||
    var user = globalRoom.users[userId];
 | 
					    var user = globalRoom.users[userId]
 | 
				
			||||||
    user.receiveMessage(m[0], m[1]);
 | 
					    user.receiveMessage(m[0], m[1])
 | 
				
			||||||
    return true;
 | 
					    return true
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return false;
 | 
					    return false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// setInterval(flushOne, 10);
 | 
					// setInterval(flushOne, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var userIdCounter = 0;
 | 
					var userIdCounter = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Test extends AbstractConnector {
 | 
					class Test extends AbstractConnector {
 | 
				
			||||||
  constructor (y, options) {
 | 
					  constructor (y, options) {
 | 
				
			||||||
    if(options === undefined){
 | 
					    if (options === undefined) {
 | 
				
			||||||
      throw new Error("Options must not be undefined!");
 | 
					      throw new Error('Options must not be undefined!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    options.role = "master";
 | 
					    options.role = 'master'
 | 
				
			||||||
    options.forwardToSyncingClients = false;
 | 
					    options.forwardToSyncingClients = false
 | 
				
			||||||
    super(y, options);
 | 
					    super(y, options)
 | 
				
			||||||
    this.setUserId((userIdCounter++) + "");
 | 
					    this.setUserId((userIdCounter++) + '')
 | 
				
			||||||
    globalRoom.addUser(this);
 | 
					    globalRoom.addUser(this)
 | 
				
			||||||
    this.globalRoom = globalRoom;
 | 
					    this.globalRoom = globalRoom
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  send (userId, message) {
 | 
					  send (userId, message) {
 | 
				
			||||||
    globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])));
 | 
					    globalRoom.buffers[userId].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  broadcast (message) {
 | 
					  broadcast (message) {
 | 
				
			||||||
    for (var key in globalRoom.buffers) {
 | 
					    for (var key in globalRoom.buffers) {
 | 
				
			||||||
      globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])));
 | 
					      globalRoom.buffers[key].push(JSON.parse(JSON.stringify([this.userId, message])))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  disconnect () {
 | 
					  disconnect () {
 | 
				
			||||||
    globalRoom.removeUser(this.userId);
 | 
					    globalRoom.removeUser(this.userId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  flush() {
 | 
					  flush () {
 | 
				
			||||||
    var buff = globalRoom.buffers[this.userId];
 | 
					    var buff = globalRoom.buffers[this.userId]
 | 
				
			||||||
    while (buff.length > 0) {
 | 
					    while (buff.length > 0) {
 | 
				
			||||||
      var m = buff.shift();
 | 
					      var m = buff.shift()
 | 
				
			||||||
      this.receiveMessage(m[0], m[1]);
 | 
					      this.receiveMessage(m[0], m[1])
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  flushAll () {
 | 
					  flushAll () {
 | 
				
			||||||
    var def = Promise.defer();
 | 
					    var def = Promise.defer()
 | 
				
			||||||
    // flushes may result in more created operations,
 | 
					    // flushes may result in more created operations,
 | 
				
			||||||
    // flush until there is nothing more to flush
 | 
					    // flush until there is nothing more to flush
 | 
				
			||||||
    function nextFlush() {
 | 
					    function nextFlush () {
 | 
				
			||||||
      var c = flushOne();
 | 
					      var c = flushOne()
 | 
				
			||||||
      if (c) {
 | 
					      if (c) {
 | 
				
			||||||
        while(flushOne()) {
 | 
					        while (flushOne()) {
 | 
				
			||||||
          //nop
 | 
					          // nop
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        wait().then(nextFlush);
 | 
					        wait().then(nextFlush)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        wait().then(function(){
 | 
					        wait().then(function () {
 | 
				
			||||||
          def.resolve();
 | 
					          def.resolve()
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // in the case that there are
 | 
					    // in the case that there are
 | 
				
			||||||
    // still actions that want to be performed
 | 
					    // still actions that want to be performed
 | 
				
			||||||
    wait(0).then(nextFlush);
 | 
					    wait(0).then(nextFlush)
 | 
				
			||||||
    return def.promise;
 | 
					    return def.promise
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  flushOne() {
 | 
					  flushOne () {
 | 
				
			||||||
    flushOne();
 | 
					    flushOne()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.Test = Test;
 | 
					Y.Test = Test
 | 
				
			||||||
 | 
				
			|||||||
@ -1,86 +1,87 @@
 | 
				
			|||||||
 | 
					/* global AbstractConnector, Y */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WebRTC extends AbstractConnector {
 | 
					class WebRTC extends AbstractConnector {
 | 
				
			||||||
  constructor (y, options) {
 | 
					  constructor (y, options) {
 | 
				
			||||||
    if(options === undefined){
 | 
					    if (options === undefined) {
 | 
				
			||||||
      throw new Error("Options must not be undefined!");
 | 
					      throw new Error('Options must not be undefined!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if(options.room == null) {
 | 
					    if (options.room == null) {
 | 
				
			||||||
      throw new Error("You must define a room name!");
 | 
					      throw new Error('You must define a room name!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    options.role = "slave";
 | 
					    options.role = 'slave'
 | 
				
			||||||
    super(y, options);
 | 
					    super(y, options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var room = options.room;
 | 
					    var room = options.room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var webrtcOptions = {
 | 
					    var webrtcOptions = {
 | 
				
			||||||
      url: options.url || "https://yatta.ninja:8888",
 | 
					      url: options.url || 'https://yatta.ninja:8888',
 | 
				
			||||||
      room: options.room
 | 
					      room: options.room
 | 
				
			||||||
    };
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var swr = new SimpleWebRTC(webrtcOptions); //eslint-disable-line no-undef
 | 
					    var swr = new SimpleWebRTC(webrtcOptions) // eslint-disable-line no-undef
 | 
				
			||||||
    this.swr = swr;
 | 
					    this.swr = swr
 | 
				
			||||||
    var self = this;
 | 
					    var self = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    swr.once("connectionReady", function(userId){
 | 
					    swr.once('connectionReady', function (userId) {
 | 
				
			||||||
      // SimpleWebRTC (swr) is initialized
 | 
					      // SimpleWebRTC (swr) is initialized
 | 
				
			||||||
      swr.joinRoom(room);
 | 
					      swr.joinRoom(room)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      swr.once("joinedRoom", function(){
 | 
					      swr.once('joinedRoom', function () {
 | 
				
			||||||
        self.setUserId(userId);
 | 
					        self.setUserId(userId)
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
        var i;
 | 
					        var i
 | 
				
			||||||
        // notify the connector class about all the users that already
 | 
					        // notify the connector class about all the users that already
 | 
				
			||||||
        // joined the session
 | 
					        // joined the session
 | 
				
			||||||
        for(i in self.swr.webrtc.peers){
 | 
					        for(i in self.swr.webrtc.peers){
 | 
				
			||||||
          self.userJoined(self.swr.webrtc.peers[i].id, "master");
 | 
					          self.userJoined(self.swr.webrtc.peers[i].id, "master")
 | 
				
			||||||
        }*/
 | 
					        }*/
 | 
				
			||||||
        swr.on("channelMessage", function(peer, room_, message){
 | 
					        swr.on('channelMessage', function (peer, room_, message) {
 | 
				
			||||||
          // The client received a message
 | 
					          // The client received a message
 | 
				
			||||||
          // Check if the connector is already initialized,
 | 
					          // Check if the connector is already initialized,
 | 
				
			||||||
          // only then forward the message to the connector class
 | 
					          // only then forward the message to the connector class
 | 
				
			||||||
          if(message.type != null ){
 | 
					          if (message.type != null) {
 | 
				
			||||||
            self.receiveMessage(peer.id, message.payload);
 | 
					            self.receiveMessage(peer.id, message.payload)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      swr.on("createdPeer", function(peer){
 | 
					      swr.on('createdPeer', function (peer) {
 | 
				
			||||||
        // a new peer/client joined the session.
 | 
					        // a new peer/client joined the session.
 | 
				
			||||||
        // Notify the connector class, if the connector
 | 
					        // Notify the connector class, if the connector
 | 
				
			||||||
        // is already initialized
 | 
					        // is already initialized
 | 
				
			||||||
        self.userJoined(peer.id, "master");
 | 
					        self.userJoined(peer.id, 'master')
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      swr.on("peerStreamRemoved", function(peer){
 | 
					      swr.on('peerStreamRemoved', function (peer) {
 | 
				
			||||||
        // a client left the session.
 | 
					        // a client left the session.
 | 
				
			||||||
        // Notify the connector class, if the connector
 | 
					        // Notify the connector class, if the connector
 | 
				
			||||||
        // is already initialized
 | 
					        // is already initialized
 | 
				
			||||||
        self.userLeft(peer.id);
 | 
					        self.userLeft(peer.id)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  send (uid, message) {
 | 
					  send (uid, message) {
 | 
				
			||||||
    var self = this;
 | 
					    var self = this
 | 
				
			||||||
    // we have to make sure that the message is sent under all circumstances
 | 
					    // we have to make sure that the message is sent under all circumstances
 | 
				
			||||||
    var send = function(){
 | 
					    var send = function () {
 | 
				
			||||||
      // check if the clients still exists
 | 
					      // check if the clients still exists
 | 
				
			||||||
      var peer = self.swr.webrtc.getPeers(uid)[0];
 | 
					      var peer = self.swr.webrtc.getPeers(uid)[0]
 | 
				
			||||||
      var success;
 | 
					      var success
 | 
				
			||||||
      if(peer){
 | 
					      if (peer) {
 | 
				
			||||||
        // success is true, if the message is successfully sent
 | 
					        // success is true, if the message is successfully sent
 | 
				
			||||||
        success = peer.sendDirectly("simplewebrtc", "yjs", message);
 | 
					        success = peer.sendDirectly('simplewebrtc', 'yjs', message)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if(!success){
 | 
					      if (!success) {
 | 
				
			||||||
        // resend the message if it didn't work
 | 
					        // resend the message if it didn't work
 | 
				
			||||||
        setTimeout(send, 500);
 | 
					        setTimeout(send, 500)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    // try to send the message
 | 
					    // try to send the message
 | 
				
			||||||
    send();
 | 
					    send()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  broadcast (message) {
 | 
					  broadcast (message) {
 | 
				
			||||||
    this.swr.sendDirectlyToAll("simplewebrtc", "yjs", message);
 | 
					    this.swr.sendDirectlyToAll('simplewebrtc', 'yjs', message)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.WebRTC = WebRTC;
 | 
					Y.WebRTC = WebRTC
 | 
				
			||||||
 | 
				
			|||||||
@ -1,107 +1,107 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global Y */
 | 
				
			||||||
/*eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***
 | 
					/*
 | 
				
			||||||
  This is "just" a compilation of functions that help to test this library!
 | 
					  This is just a compilation of functions that help to test this library!
 | 
				
			||||||
***/
 | 
					***/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function wait(t = 0) {//eslint-disable-line
 | 
					function wait(t = 0) {//eslint-disable-line
 | 
				
			||||||
  var def = Promise.defer();
 | 
					  var def = Promise.defer()
 | 
				
			||||||
  setTimeout(function(){
 | 
					  setTimeout(function () {
 | 
				
			||||||
    def.resolve();
 | 
					    def.resolve()
 | 
				
			||||||
  }, t);
 | 
					  }, t)
 | 
				
			||||||
  return def.promise;
 | 
					  return def.promise
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// returns a random element of o
 | 
					// returns a random element of o
 | 
				
			||||||
// works on Object, and Array
 | 
					// works on Object, and Array
 | 
				
			||||||
function getRandom (o) {
 | 
					function getRandom (o) {
 | 
				
			||||||
  if (o instanceof Array) {
 | 
					  if (o instanceof Array) {
 | 
				
			||||||
    return o[Math.floor(Math.random() * o.length)];
 | 
					    return o[Math.floor(Math.random() * o.length)]
 | 
				
			||||||
  } else if (o.constructor === Object) {
 | 
					  } else if (o.constructor === Object) {
 | 
				
			||||||
    var ks = [];
 | 
					    var ks = []
 | 
				
			||||||
    for (var key in o) {
 | 
					    for (var key in o) {
 | 
				
			||||||
      ks.push(key);
 | 
					      ks.push(key)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return o[getRandom(ks)];
 | 
					    return o[getRandom(ks)]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
function getRandomNumber(n) {//eslint-disable-line
 | 
					function getRandomNumber(n) {//eslint-disable-line
 | 
				
			||||||
  if (n == null) {
 | 
					  if (n == null) {
 | 
				
			||||||
    n = 9999;
 | 
					    n = 9999
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return Math.floor(Math.random() * n);
 | 
					  return Math.floor(Math.random() * n)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function applyRandomTransactions (users, objects, transactions, numberOfTransactions) {//eslint-disable-line
 | 
					async function applyRandomTransactions (users, objects, transactions, numberOfTransactions) {//eslint-disable-line
 | 
				
			||||||
  function randomTransaction (root) {
 | 
					  function randomTransaction (root) {
 | 
				
			||||||
    var f = getRandom(transactions);
 | 
					    var f = getRandom(transactions)
 | 
				
			||||||
    f(root);
 | 
					    f(root)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  for(var i = 0; i < numberOfTransactions; i++) {
 | 
					  for (var i = 0; i < numberOfTransactions; i++) {
 | 
				
			||||||
    var r = Math.random();
 | 
					    var r = Math.random()
 | 
				
			||||||
    if (r >= 0.9) {
 | 
					    if (r >= 0.9) {
 | 
				
			||||||
      // 10% chance to flush
 | 
					      // 10% chance to flush
 | 
				
			||||||
      users[0].connector.flushOne();
 | 
					      users[0].connector.flushOne()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      randomTransaction(getRandom(objects));
 | 
					      randomTransaction(getRandom(objects))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    wait();
 | 
					    wait()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function compareAllUsers(users){//eslint-disable-line
 | 
					async function compareAllUsers(users){//eslint-disable-line
 | 
				
			||||||
  var s1, s2;
 | 
					  var s1, s2
 | 
				
			||||||
  var db1 = [];
 | 
					  var db1 = []
 | 
				
			||||||
  function* t1(){
 | 
					  function * t1 () {
 | 
				
			||||||
    s1 = yield* this.getStateSet();
 | 
					    s1 = yield* this.getStateSet()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  function* t2(){
 | 
					  function * t2 () {
 | 
				
			||||||
    s2 = yield* this.getStateSet();
 | 
					    s2 = yield* this.getStateSet()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  await users[0].connector.flushAll();
 | 
					  await users[0].connector.flushAll()
 | 
				
			||||||
  for (var uid = 0; uid < users.length; uid++) {
 | 
					  for (var uid = 0; uid < users.length; uid++) {
 | 
				
			||||||
    if (s1 == null) {
 | 
					    if (s1 == null) {
 | 
				
			||||||
      var u = users[uid];
 | 
					      var u = users[uid]
 | 
				
			||||||
      u.db.requestTransaction(t1);
 | 
					      u.db.requestTransaction(t1)
 | 
				
			||||||
      await wait();
 | 
					      await wait()
 | 
				
			||||||
      u.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
					      u.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
				
			||||||
        db1.push(o);
 | 
					        db1.push(o)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      var u2 = users[uid];
 | 
					      var u2 = users[uid]
 | 
				
			||||||
      u2.db.requestTransaction(t2);
 | 
					      u2.db.requestTransaction(t2)
 | 
				
			||||||
      await wait();
 | 
					      await wait()
 | 
				
			||||||
      expect(s1).toEqual(s2);
 | 
					      expect(s1).toEqual(s2)
 | 
				
			||||||
      var count = 0;
 | 
					      var count = 0
 | 
				
			||||||
      u2.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
					      u2.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
				
			||||||
        expect(db1[count++]).toEqual(o);
 | 
					        expect(db1[count++]).toEqual(o)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function createUsers(self, numberOfUsers) {//eslint-disable-line
 | 
					async function createUsers(self, numberOfUsers) {//eslint-disable-line
 | 
				
			||||||
  if (globalRoom.users[0] != null) {//eslint-disable-line
 | 
					  if (globalRoom.users[0] != null) {//eslint-disable-line
 | 
				
			||||||
    await globalRoom.users[0].flushAll();//eslint-disable-line
 | 
					    await globalRoom.users[0].flushAll()//eslint-disable-line
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  //destroy old users
 | 
					  // destroy old users
 | 
				
			||||||
  for (var u in globalRoom.users) {//eslint-disable-line
 | 
					  for (var u in globalRoom.users) {//eslint-disable-line
 | 
				
			||||||
    globalRoom.users[u].y.destroy()//eslint-disable-line
 | 
					    globalRoom.users[u].y.destroy()//eslint-disable-line
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  self.users = [];
 | 
					  self.users = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var promises = [];
 | 
					  var promises = []
 | 
				
			||||||
  for (var i = 0; i < numberOfUsers; i++) {
 | 
					  for (var i = 0; i < numberOfUsers; i++) {
 | 
				
			||||||
    promises.push(Y({
 | 
					    promises.push(Y({
 | 
				
			||||||
      db: {
 | 
					      db: {
 | 
				
			||||||
        name: "Memory"
 | 
					        name: 'Memory'
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      connector: {
 | 
					      connector: {
 | 
				
			||||||
        name: "Test",
 | 
					        name: 'Test',
 | 
				
			||||||
        debug: false
 | 
					        debug: false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }));
 | 
					    }))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  self.users = await Promise.all(promises);
 | 
					  self.users = await Promise.all(promises)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,58 +1,52 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global Y, copyObject, Struct, RBTree */
 | 
				
			||||||
class AbstractTransaction { //eslint-disable-line no-unused-vars
 | 
					
 | 
				
			||||||
  constructor (store : OperationStore) {
 | 
					class AbstractTransaction { // eslint-disable-line no-unused-vars
 | 
				
			||||||
    this.store = store;
 | 
					  constructor (store) {
 | 
				
			||||||
 | 
					    this.store = store
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  *getType (id) {
 | 
					  * getType (id) {
 | 
				
			||||||
    var sid = JSON.stringify(id);
 | 
					    var sid = JSON.stringify(id)
 | 
				
			||||||
    var t = this.store.initializedTypes[sid];
 | 
					    var t = this.store.initializedTypes[sid]
 | 
				
			||||||
    if (t == null) {
 | 
					    if (t == null) {
 | 
				
			||||||
      var op = yield* this.getOperation(id);
 | 
					      var op = yield* this.getOperation(id)
 | 
				
			||||||
      if (op != null) {
 | 
					      if (op != null) {
 | 
				
			||||||
        t = yield* Y[op.type].initType.call(this, this.store, op);
 | 
					        t = yield* Y[op.type].initType.call(this, this.store, op)
 | 
				
			||||||
        this.store.initializedTypes[sid] = t;
 | 
					        this.store.initializedTypes[sid] = t
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return t;
 | 
					    return t
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  *createType (model) {
 | 
					  * createType (model) {
 | 
				
			||||||
    var sid = JSON.stringify(model.id);
 | 
					    var sid = JSON.stringify(model.id)
 | 
				
			||||||
    var t = yield* Y[model.type].initType.call(this, this.store, model);
 | 
					    var t = yield* Y[model.type].initType.call(this, this.store, model)
 | 
				
			||||||
    this.store.initializedTypes[sid] = t;
 | 
					    this.store.initializedTypes[sid] = t
 | 
				
			||||||
    return t;
 | 
					    return t
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  *applyCreatedOperations (ops) {
 | 
					  * applyCreatedOperations (ops) {
 | 
				
			||||||
    var send = [];
 | 
					    var send = []
 | 
				
			||||||
    for (var i = 0; i < ops.length; i++) {
 | 
					    for (var i = 0; i < ops.length; i++) {
 | 
				
			||||||
      var op = ops[i];
 | 
					      var op = ops[i]
 | 
				
			||||||
      yield* this.store.tryExecute.call(this, op);
 | 
					      yield* this.store.tryExecute.call(this, op)
 | 
				
			||||||
      send.push(copyObject(Struct[op.struct].encode(op)));
 | 
					      send.push(copyObject(Struct[op.struct].encode(op)))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.store.y.connector.broadcastedHB){
 | 
					    if (this.store.y.connector.broadcastedHB) {
 | 
				
			||||||
      this.store.y.connector.broadcast({
 | 
					      this.store.y.connector.broadcast({
 | 
				
			||||||
        type: "update",
 | 
					        type: 'update',
 | 
				
			||||||
        ops: send
 | 
					        ops: send
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Listener = {
 | 
					class AbstractOperationStore { // eslint-disable-line no-unused-vars
 | 
				
			||||||
  f : GeneratorFunction, // is called when all operations are available
 | 
					 | 
				
			||||||
  missing : number // number of operations that are missing
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Id = [string, number];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AbstractOperationStore { //eslint-disable-line no-unused-vars
 | 
					 | 
				
			||||||
  constructor (y) {
 | 
					  constructor (y) {
 | 
				
			||||||
    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>
 | 
				
			||||||
@ -65,163 +59,163 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
    */
 | 
					    */
 | 
				
			||||||
    // 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.waitingOperations = new RBTree();
 | 
					    this.waitingOperations = new RBTree()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  setUserId (userId) {
 | 
					  setUserId (userId) {
 | 
				
			||||||
    this.userId = userId;
 | 
					    this.userId = userId
 | 
				
			||||||
    this.opClock = 0;
 | 
					    this.opClock = 0
 | 
				
			||||||
    if (this.whenUserIdSetListener != null) {
 | 
					    if (this.whenUserIdSetListener != null) {
 | 
				
			||||||
      this.whenUserIdSetListener();
 | 
					      this.whenUserIdSetListener()
 | 
				
			||||||
      this.whenUserIdSetListener = null;
 | 
					      this.whenUserIdSetListener = null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  whenUserIdSet (f) {
 | 
					  whenUserIdSet (f) {
 | 
				
			||||||
    if (this.userId != null) {
 | 
					    if (this.userId != null) {
 | 
				
			||||||
      f();
 | 
					      f()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.whenUserIdSetListener = f;
 | 
					      this.whenUserIdSetListener = f
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  getNextOpId () {
 | 
					  getNextOpId () {
 | 
				
			||||||
    if (this.userId == null) {
 | 
					    if (this.userId == null) {
 | 
				
			||||||
      throw new Error("OperationStore not yet initialized!");
 | 
					      throw new Error('OperationStore not yet initialized!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [this.userId, this.opClock++];
 | 
					    return [this.userId, this.opClock++]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  apply (ops) {
 | 
					  apply (ops) {
 | 
				
			||||||
    for (var key in ops) {
 | 
					    for (var key in ops) {
 | 
				
			||||||
      var o = ops[key];
 | 
					      var o = ops[key]
 | 
				
			||||||
      var required = Struct[o.struct].requiredOps(o);
 | 
					      var required = Struct[o.struct].requiredOps(o)
 | 
				
			||||||
      this.whenOperationsExist(required, o);
 | 
					      this.whenOperationsExist(required, o)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // op is executed as soon as every operation requested is available.
 | 
					  // op is executed as soon as every operation requested is available.
 | 
				
			||||||
  // Note that Transaction can (and should) buffer requests.
 | 
					  // Note that Transaction can (and should) buffer requests.
 | 
				
			||||||
  whenOperationsExist (ids : Array<Id>, op : Operation) {
 | 
					  whenOperationsExist (ids, op) {
 | 
				
			||||||
    if (ids.length > 0) {
 | 
					    if (ids.length > 0) {
 | 
				
			||||||
      let listener : Listener = {
 | 
					      let listener = {
 | 
				
			||||||
        op: op,
 | 
					        op: op,
 | 
				
			||||||
        missing: ids.length
 | 
					        missing: ids.length
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (let key in ids) {
 | 
					      for (let key in ids) {
 | 
				
			||||||
        let id = ids[key];
 | 
					        let id = ids[key]
 | 
				
			||||||
        let sid = JSON.stringify(id);
 | 
					        let sid = JSON.stringify(id)
 | 
				
			||||||
        let l = this.listenersById[sid];
 | 
					        let l = this.listenersById[sid]
 | 
				
			||||||
        if (l == null){
 | 
					        if (l == null) {
 | 
				
			||||||
          l = [];
 | 
					          l = []
 | 
				
			||||||
          this.listenersById[sid] = l;
 | 
					          this.listenersById[sid] = l
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        l.push(listener);
 | 
					        l.push(listener)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.listenersByIdExecuteNow.push({
 | 
					      this.listenersByIdExecuteNow.push({
 | 
				
			||||||
        op: op
 | 
					        op: op
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.listenersByIdRequestPending){
 | 
					    if (this.listenersByIdRequestPending) {
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.listenersByIdRequestPending = true;
 | 
					    this.listenersByIdRequestPending = true
 | 
				
			||||||
    var store = this;
 | 
					    var store = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.requestTransaction(function*(){
 | 
					    this.requestTransaction(function *() {
 | 
				
			||||||
      var exeNow = store.listenersByIdExecuteNow;
 | 
					      var exeNow = store.listenersByIdExecuteNow
 | 
				
			||||||
      store.listenersByIdExecuteNow = [];
 | 
					      store.listenersByIdExecuteNow = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var ls = store.listenersById;
 | 
					      var ls = store.listenersById
 | 
				
			||||||
      store.listenersById = {};
 | 
					      store.listenersById = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      store.listenersByIdRequestPending = false;
 | 
					      store.listenersByIdRequestPending = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (let key in exeNow) {
 | 
					      for (let key in exeNow) {
 | 
				
			||||||
        let o = exeNow[key].op;
 | 
					        let o = exeNow[key].op
 | 
				
			||||||
        yield* store.tryExecute.call(this, o);
 | 
					        yield* store.tryExecute.call(this, o)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var sid in ls){
 | 
					      for (var sid in ls) {
 | 
				
			||||||
        var l = ls[sid];
 | 
					        var l = ls[sid]
 | 
				
			||||||
        var id = JSON.parse(sid);
 | 
					        var id = JSON.parse(sid)
 | 
				
			||||||
        if ((yield* this.getOperation(id)) == null){
 | 
					        if ((yield* this.getOperation(id)) == null) {
 | 
				
			||||||
          store.listenersById[sid] = l;
 | 
					          store.listenersById[sid] = l
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          for (let key in l) {
 | 
					          for (let key in l) {
 | 
				
			||||||
            let listener = l[key];
 | 
					            let listener = l[key]
 | 
				
			||||||
            let o = listener.op;
 | 
					            let o = listener.op
 | 
				
			||||||
            if (--listener.missing === 0){
 | 
					            if (--listener.missing === 0) {
 | 
				
			||||||
              yield* store.tryExecute.call(this, o);
 | 
					              yield* store.tryExecute.call(this, o)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  *tryExecute (op) {
 | 
					  * tryExecute (op) {
 | 
				
			||||||
    if (op.struct === "Delete") {
 | 
					    if (op.struct === 'Delete') {
 | 
				
			||||||
      yield* Struct.Delete.execute.call(this, op);
 | 
					      yield* Struct.Delete.execute.call(this, op)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      while (op != null) {
 | 
					      while (op != null) {
 | 
				
			||||||
        var state = yield* this.getState(op.id[0]);
 | 
					        var state = yield* this.getState(op.id[0])
 | 
				
			||||||
        if (op.id[1] === state.clock){
 | 
					        if (op.id[1] === state.clock) {
 | 
				
			||||||
          state.clock++;
 | 
					          state.clock++
 | 
				
			||||||
          yield* this.setState.call(this, state);
 | 
					          yield* this.setState(state)
 | 
				
			||||||
          yield* Struct[op.struct].execute.call(this, op);
 | 
					          yield* Struct[op.struct].execute.call(this, op)
 | 
				
			||||||
          yield* this.addOperation(op);
 | 
					          yield* this.addOperation(op)
 | 
				
			||||||
          yield* this.store.operationAdded(this, op);
 | 
					          yield* this.store.operationAdded(this, op)
 | 
				
			||||||
          // find next operation to execute
 | 
					          // find next operation to execute
 | 
				
			||||||
          op = this.store.waitingOperations.find([op.id[0], state.clock]);
 | 
					          op = this.store.waitingOperations.find([op.id[0], state.clock])
 | 
				
			||||||
          if (op != null) {
 | 
					          if (op != null) {
 | 
				
			||||||
            this.store.waitingOperations.delete([op.id[0], state.clock]);
 | 
					            this.store.waitingOperations.delete([op.id[0], state.clock])
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          if (op.id[1] > state.clock) {
 | 
					          if (op.id[1] > state.clock) {
 | 
				
			||||||
            // has to be executed at some point later
 | 
					            // has to be executed at some point later
 | 
				
			||||||
            this.store.waitingOperations.add(op);
 | 
					            this.store.waitingOperations.add(op)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          op = null;
 | 
					          op = null
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // called by a transaction when an operation is added
 | 
					  // called by a transaction when an operation is added
 | 
				
			||||||
  *operationAdded (transaction, op) {
 | 
					  * operationAdded (transaction, op) {
 | 
				
			||||||
    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]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // notify whenOperation listeners (by id)
 | 
					    // notify whenOperation listeners (by id)
 | 
				
			||||||
    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)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // notify parent, if it has been initialized as a custom type
 | 
					    // notify parent, if it has been initialized as a custom type
 | 
				
			||||||
    var t = this.initializedTypes[JSON.stringify(op.parent)];
 | 
					    var t = this.initializedTypes[JSON.stringify(op.parent)]
 | 
				
			||||||
    if (t != null) {
 | 
					    if (t != null) {
 | 
				
			||||||
      yield* t._changed(transaction, copyObject(op));
 | 
					      yield* t._changed(transaction, copyObject(op))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  removeParentListener (id, f) {
 | 
					  removeParentListener (id, f) {
 | 
				
			||||||
    var ls = this.parentListeners[id];
 | 
					    var ls = this.parentListeners[id]
 | 
				
			||||||
    if (ls != null) {
 | 
					    if (ls != null) {
 | 
				
			||||||
      this.parentListeners[id] = ls.filter(function(g){
 | 
					      this.parentListeners[id] = ls.filter(function (g) {
 | 
				
			||||||
        return (f !== g);
 | 
					        return (f !== g)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  addParentListener (id, f) {
 | 
					  addParentListener (id, f) {
 | 
				
			||||||
    var ls = this.parentListeners[JSON.stringify(id)];
 | 
					    var ls = this.parentListeners[JSON.stringify(id)]
 | 
				
			||||||
    if (ls == null) {
 | 
					    if (ls == null) {
 | 
				
			||||||
      ls = [];
 | 
					      ls = []
 | 
				
			||||||
      this.parentListeners[JSON.stringify(id)] = ls;
 | 
					      this.parentListeners[JSON.stringify(id)] = ls
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ls.push(f);
 | 
					    ls.push(f)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
/* @flow */
 | 
					 | 
				
			||||||
/*eslint-env browser,jasmine,console */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe("OperationStore", function() {
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@ -1,208 +1,179 @@
 | 
				
			|||||||
 | 
					Y.IndexedDB = (function () { // eslint-disable-line
 | 
				
			||||||
type State = {
 | 
					  class Transaction extends AbstractTransaction { // eslint-disable-line
 | 
				
			||||||
  user: string,
 | 
					    constructor (store) {
 | 
				
			||||||
  clock: number
 | 
					      super(store)
 | 
				
			||||||
};
 | 
					      this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite')
 | 
				
			||||||
 | 
					      this.sv = this.transaction.objectStore('StateVector')
 | 
				
			||||||
type StateVector = Array<State>;
 | 
					      this.os = this.transaction.objectStore('OperationStore')
 | 
				
			||||||
 | 
					      this.buffer = {}
 | 
				
			||||||
type StateSet = Object;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type IDBTransaction = Function;
 | 
					 | 
				
			||||||
type IDBObjectStore = Function;
 | 
					 | 
				
			||||||
type IDBRequest = Function;
 | 
					 | 
				
			||||||
type IDBCursor = Function;
 | 
					 | 
				
			||||||
type IDBKeyRange = Function;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type IDBOpenDBRequest = Function;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare var indexedDB : Object;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
 | 
					 | 
				
			||||||
  class Transaction extends AbstractTransaction { //eslint-disable-line
 | 
					 | 
				
			||||||
    transaction: IDBTransaction;
 | 
					 | 
				
			||||||
    sv: IDBObjectStore;
 | 
					 | 
				
			||||||
    os: IDBObjectStore;
 | 
					 | 
				
			||||||
    store: OperationStore;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor (store : OperationStore) {
 | 
					 | 
				
			||||||
      super(store);
 | 
					 | 
				
			||||||
      this.transaction = store.db.transaction(["OperationStore", "StateVector"], "readwrite");
 | 
					 | 
				
			||||||
      this.sv = this.transaction.objectStore("StateVector");
 | 
					 | 
				
			||||||
      this.os = this.transaction.objectStore("OperationStore");
 | 
					 | 
				
			||||||
      this.buffer = {};
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *setOperation (op) {
 | 
					    * setOperation (op) {
 | 
				
			||||||
      yield this.os.put(op);
 | 
					      yield this.os.put(op)
 | 
				
			||||||
      this.buffer[JSON.stringify(op.id)] = op;
 | 
					      this.buffer[JSON.stringify(op.id)] = op
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getOperation (id) {
 | 
					    * getOperation (id) {
 | 
				
			||||||
      var op = this.buffer[JSON.stringify(id)];
 | 
					      var op = this.buffer[JSON.stringify(id)]
 | 
				
			||||||
      if (op == null) {
 | 
					      if (op == null) {
 | 
				
			||||||
        op = yield this.os.get(id);
 | 
					        op = yield this.os.get(id)
 | 
				
			||||||
        this.buffer[JSON.stringify(id)] = op;
 | 
					        this.buffer[JSON.stringify(id)] = op
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *removeOperation (id) {
 | 
					    * removeOperation (id) {
 | 
				
			||||||
      this.buffer[JSON.stringify(id)] = null;
 | 
					      this.buffer[JSON.stringify(id)] = null
 | 
				
			||||||
      return yield this.os.delete(id);
 | 
					      return yield this.os.delete(id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *setState (state : State) : State {
 | 
					    * setState (state) {
 | 
				
			||||||
      return yield this.sv.put(state);
 | 
					      return yield this.sv.put(state)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getState (user : string) : State {
 | 
					    * getState (user) {
 | 
				
			||||||
      var state;
 | 
					      var state
 | 
				
			||||||
      if ((state = yield this.sv.get(user)) != null){
 | 
					      if ((state = yield this.sv.get(user)) != null) {
 | 
				
			||||||
        return state;
 | 
					        return state
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          user: user,
 | 
					          user: user,
 | 
				
			||||||
          clock: 0
 | 
					          clock: 0
 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    *getStateVector () : StateVector {
 | 
					    }
 | 
				
			||||||
      var stateVector = [];
 | 
					    * getStateVector () {
 | 
				
			||||||
      var cursorResult = this.sv.openCursor();
 | 
					      var stateVector = []
 | 
				
			||||||
      var cursor;
 | 
					      var cursorResult = this.sv.openCursor()
 | 
				
			||||||
 | 
					      var cursor
 | 
				
			||||||
      while ((cursor = yield cursorResult) != null) {
 | 
					      while ((cursor = yield cursorResult) != null) {
 | 
				
			||||||
        stateVector.push(cursor.value);
 | 
					        stateVector.push(cursor.value)
 | 
				
			||||||
        cursor.continue();
 | 
					        cursor.continue()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return stateVector;
 | 
					      return stateVector
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getStateSet () : StateSet {
 | 
					    * getStateSet () {
 | 
				
			||||||
      var sv : StateVector = yield* this.getStateVector();
 | 
					      var sv = yield* this.getStateVector()
 | 
				
			||||||
      var ss : StateSet = {};
 | 
					      var ss = {}
 | 
				
			||||||
      for (var state of sv){
 | 
					      for (var state of sv) {
 | 
				
			||||||
        ss[state.user] = state.clock;
 | 
					        ss[state.user] = state.clock
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return ss;
 | 
					      return ss
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    *getOperations (startSS : StateSet) {
 | 
					    * getOperations (startSS) {
 | 
				
			||||||
      if (startSS == null){
 | 
					      if (startSS == null) {
 | 
				
			||||||
        startSS = {};
 | 
					        startSS = {}
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var ops = [];
 | 
					      var ops = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var endSV : StateVector = yield* this.getStateVector();
 | 
					      var endSV = yield* this.getStateVector()
 | 
				
			||||||
      for (var endState of endSV) {
 | 
					      for (var endState of endSV) {
 | 
				
			||||||
        var user = endState.user;
 | 
					        var user = endState.user
 | 
				
			||||||
        var startPos = startSS[user] || 0;
 | 
					        var startPos = startSS[user] || 0
 | 
				
			||||||
        var endPos = endState.clock;
 | 
					        var endPos = endState.clock
 | 
				
			||||||
        var range = IDBKeyRange.bound([user, startPos], [user, endPos]);
 | 
					        var range = window.IDBKeyRange.bound([user, startPos], [user, endPos])
 | 
				
			||||||
        var cursorResult = this.os.openCursor(range);
 | 
					        var cursorResult = this.os.openCursor(range)
 | 
				
			||||||
        var cursor;
 | 
					        var cursor
 | 
				
			||||||
        while ((cursor = yield cursorResult) != null) {
 | 
					        while ((cursor = yield cursorResult) != null) {
 | 
				
			||||||
          ops.push(cursor.value);
 | 
					          ops.push(cursor.value)
 | 
				
			||||||
          cursor.continue();
 | 
					          cursor.continue()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return ops;
 | 
					      return ops
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
 | 
					  class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
 | 
				
			||||||
    namespace: string;
 | 
					 | 
				
			||||||
    ready: Promise;
 | 
					 | 
				
			||||||
    whenReadyListeners: Array<Function>;
 | 
					 | 
				
			||||||
    constructor (y, opts) {
 | 
					    constructor (y, opts) {
 | 
				
			||||||
      super(y);
 | 
					      super(y)
 | 
				
			||||||
      if (opts == null) {
 | 
					      if (opts == null) {
 | 
				
			||||||
        opts = {};
 | 
					        opts = {}
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (opts.namespace == null || typeof opts.namespace !== "string") {
 | 
					      if (opts.namespace == null || typeof opts.namespace !== 'string') {
 | 
				
			||||||
        throw new Error("IndexedDB: expect a string (opts.namespace)!");
 | 
					        throw new Error('IndexedDB: expect a string (opts.namespace)!')
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.namespace = opts.namespace;
 | 
					        this.namespace = opts.namespace
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (opts.idbVersion != null) {
 | 
					      if (opts.idbVersion != null) {
 | 
				
			||||||
        this.idbVersion = opts.idbVersion;
 | 
					        this.idbVersion = opts.idbVersion
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.idbVersion = 5;
 | 
					        this.idbVersion = 5
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.transactionQueue = {
 | 
					      this.transactionQueue = {
 | 
				
			||||||
        queue: [],
 | 
					        queue: [],
 | 
				
			||||||
        onRequest: null
 | 
					        onRequest: null
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var store = this;
 | 
					      var store = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var tGen = (function *transactionGen(){
 | 
					      var tGen = (function * transactionGen () {
 | 
				
			||||||
        store.db = yield indexedDB.open(opts.namespace, store.idbVersion);
 | 
					        store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
 | 
				
			||||||
        var transactionQueue = store.transactionQueue;
 | 
					        var transactionQueue = store.transactionQueue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var transaction = null;
 | 
					        var transaction = null
 | 
				
			||||||
        var cont = true;
 | 
					        var cont = true
 | 
				
			||||||
        while (cont) {
 | 
					        while (cont) {
 | 
				
			||||||
          var request = yield transactionQueue;
 | 
					          var request = yield transactionQueue
 | 
				
			||||||
          transaction = new Transaction(store);
 | 
					          transaction = new Transaction(store)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          yield* request.call(transaction, request);/*
 | 
					          yield* request.call(transaction, request) /*
 | 
				
			||||||
          while (transactionQueue.queue.length > 0) {
 | 
					          while (transactionQueue.queue.length > 0) {
 | 
				
			||||||
            yield* transactionQueue.queue.shift().call(transaction);
 | 
					            yield* transactionQueue.queue.shift().call(transaction)
 | 
				
			||||||
          }*/
 | 
					          }*/
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })();
 | 
					      })()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      function handleTransactions(t){ //eslint-disable-line no-unused-vars
 | 
					      function handleTransactions (t) { // eslint-disable-line no-unused-vars
 | 
				
			||||||
        var request = t.value;
 | 
					        var request = t.value
 | 
				
			||||||
        if (t.done){
 | 
					        if (t.done) {
 | 
				
			||||||
          return;
 | 
					          return
 | 
				
			||||||
        } else if (request.constructor === IDBRequest
 | 
					        } else if (request.constructor === window.IDBRequest || request.constructor === window.IDBCursor) {
 | 
				
			||||||
                   || request.constructor === IDBCursor ) {
 | 
					          request.onsuccess = function () {
 | 
				
			||||||
          request.onsuccess = function(){
 | 
					            handleTransactions(tGen.next(request.result))
 | 
				
			||||||
            handleTransactions(tGen.next(request.result));
 | 
					          }
 | 
				
			||||||
          };
 | 
					          request.onerror = function (err) {
 | 
				
			||||||
          request.onerror = function(err){
 | 
					            tGen.throw(err)
 | 
				
			||||||
            tGen.throw(err);
 | 
					          }
 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        } else if (request === store.transactionQueue) {
 | 
					        } else if (request === store.transactionQueue) {
 | 
				
			||||||
          if (request.queue.length > 0){
 | 
					          if (request.queue.length > 0) {
 | 
				
			||||||
            handleTransactions(tGen.next(request.queue.shift()));
 | 
					            handleTransactions(tGen.next(request.queue.shift()))
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            request.onRequest = function(){
 | 
					            request.onRequest = function () {
 | 
				
			||||||
              request.onRequest = null;
 | 
					              request.onRequest = null
 | 
				
			||||||
              handleTransactions(tGen.next(request.queue.shift()));
 | 
					              handleTransactions(tGen.next(request.queue.shift()))
 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if ( request.constructor === IDBOpenDBRequest ) {
 | 
					          }
 | 
				
			||||||
          request.onsuccess = function(event){
 | 
					        } else if (request.constructor === window.IDBOpenDBRequest) {
 | 
				
			||||||
            var db = event.target.result;
 | 
					          request.onsuccess = function (event) {
 | 
				
			||||||
            handleTransactions(tGen.next(db));
 | 
					            var db = event.target.result
 | 
				
			||||||
          };
 | 
					            handleTransactions(tGen.next(db))
 | 
				
			||||||
          request.onerror = function(){
 | 
					          }
 | 
				
			||||||
            tGen.throw("Couldn't open IndexedDB database!");
 | 
					          request.onerror = function () {
 | 
				
			||||||
          };
 | 
					            tGen.throw("Couldn't open IndexedDB database!")
 | 
				
			||||||
          request.onupgradeneeded = function(event){
 | 
					          }
 | 
				
			||||||
            var db = event.target.result;
 | 
					          request.onupgradeneeded = function (event) {
 | 
				
			||||||
 | 
					            var db = event.target.result
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
              db.createObjectStore("OperationStore", {keyPath: "id"});
 | 
					              db.createObjectStore('OperationStore', {keyPath: 'id'})
 | 
				
			||||||
              db.createObjectStore("StateVector", {keyPath: "user"});
 | 
					              db.createObjectStore('StateVector', {keyPath: 'user'})
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
                // console.log("Store already exists!");
 | 
					              // console.log("Store already exists!")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          tGen.throw("You can not yield this type!");
 | 
					          tGen.throw('You can not yield this type!')
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      handleTransactions(tGen.next());
 | 
					      handleTransactions(tGen.next())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    requestTransaction (makeGen : Function) {
 | 
					    requestTransaction (makeGen) {
 | 
				
			||||||
      this.transactionQueue.queue.push(makeGen);
 | 
					      this.transactionQueue.queue.push(makeGen)
 | 
				
			||||||
      if (this.transactionQueue.onRequest != null) {
 | 
					      if (this.transactionQueue.onRequest != null) {
 | 
				
			||||||
        this.transactionQueue.onRequest();
 | 
					        this.transactionQueue.onRequest()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *removeDatabase () {
 | 
					    * removeDatabase () {
 | 
				
			||||||
      this.db.close();
 | 
					      this.db.close()
 | 
				
			||||||
      yield indexedDB.deleteDatabase(this.namespace);
 | 
					      yield window.indexedDB.deleteDatabase(this.namespace)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return OperationStore;
 | 
					  return OperationStore
 | 
				
			||||||
})();
 | 
					})()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,117 +1,117 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global Y */
 | 
				
			||||||
/*eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if(typeof window !== "undefined"){
 | 
					if (typeof window !== 'undefined') {
 | 
				
			||||||
  jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
 | 
					  jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000
 | 
				
			||||||
  describe("IndexedDB", function() {
 | 
					  describe('IndexedDB', function () {
 | 
				
			||||||
    var ob;
 | 
					    var ob
 | 
				
			||||||
    beforeAll(function(){
 | 
					    beforeAll(function () {
 | 
				
			||||||
      ob = new Y.IndexedDB(null, {namespace: "Test"});
 | 
					      ob = new Y.IndexedDB(null, {namespace: 'Test'})
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("can add and get operation", function(done) {
 | 
					    it('can add and get operation', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var op = yield* this.setOperation({
 | 
					        var op = yield* this.setOperation({
 | 
				
			||||||
          "id": ["1", 0],
 | 
					          'id': ['1', 0],
 | 
				
			||||||
          "stuff": true
 | 
					          'stuff': true
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        expect(yield* this.getOperation(["1", 0]))
 | 
					        expect(yield* this.getOperation(['1', 0]))
 | 
				
			||||||
          .toEqual(op);
 | 
					          .toEqual(op)
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("can remove operation", function(done) {
 | 
					    it('can remove operation', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var op = yield* this.setOperation({
 | 
					        var op = yield* this.setOperation({
 | 
				
			||||||
          "id": ["1", 0],
 | 
					          'id': ['1', 0],
 | 
				
			||||||
          "stuff": true
 | 
					          'stuff': true
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        expect(yield* this.getOperation(["1", 0]))
 | 
					        expect(yield* this.getOperation(['1', 0]))
 | 
				
			||||||
          .toEqual(op);
 | 
					          .toEqual(op)
 | 
				
			||||||
        yield* this.removeOperation(["1", 0]);
 | 
					        yield* this.removeOperation(['1', 0])
 | 
				
			||||||
        expect(yield* this.getOperation(["1", 0]))
 | 
					        expect(yield* this.getOperation(['1', 0]))
 | 
				
			||||||
          .toBeUndefined();
 | 
					          .toBeUndefined()
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("getOperation(op) returns undefined if op does not exist", function(done){
 | 
					    it('getOperation(op) returns undefined if op does not exist', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var op = yield* this.getOperation("plzDon'tBeThere");
 | 
					        var op = yield* this.getOperation("plzDon'tBeThere")
 | 
				
			||||||
        expect(op).toBeUndefined();
 | 
					        expect(op).toBeUndefined()
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("yield throws if request is unknown", function(done){
 | 
					    it('yield throws if request is unknown', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          yield* this.setOperation();
 | 
					          yield* this.setOperation()
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
          expect(true).toEqual(true);
 | 
					          expect(true).toEqual(true)
 | 
				
			||||||
          done();
 | 
					          done()
 | 
				
			||||||
          return;
 | 
					          return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        expect("Expected an Error!").toEqual(true);
 | 
					        expect('Expected an Error!').toEqual(true)
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("sets and gets stateVector", function(done){
 | 
					    it('sets and gets stateVector', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var s1 = {user: "1", clock: 1};
 | 
					        var s1 = {user: '1', clock: 1}
 | 
				
			||||||
        var s2 = {user: "2", clock: 3};
 | 
					        var s2 = {user: '2', clock: 3}
 | 
				
			||||||
        yield* this.setState(s1);
 | 
					        yield* this.setState(s1)
 | 
				
			||||||
        yield* this.setState(s2);
 | 
					        yield* this.setState(s2)
 | 
				
			||||||
        var sv = yield* this.getStateVector();
 | 
					        var sv = yield* this.getStateVector()
 | 
				
			||||||
        expect(sv).not.toBeUndefined();
 | 
					        expect(sv).not.toBeUndefined()
 | 
				
			||||||
        expect(sv).toEqual([s1, s2]);
 | 
					        expect(sv).toEqual([s1, s2])
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("gets stateSet", function(done){
 | 
					    it('gets stateSet', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var s1 = {user: "1", clock: 1};
 | 
					        var s1 = {user: '1', clock: 1}
 | 
				
			||||||
        var s2 = {user: "2", clock: 3};
 | 
					        var s2 = {user: '2', clock: 3}
 | 
				
			||||||
        yield* this.setState(s1);
 | 
					        yield* this.setState(s1)
 | 
				
			||||||
        yield* this.setState(s2);
 | 
					        yield* this.setState(s2)
 | 
				
			||||||
        var sv = yield* this.getStateSet();
 | 
					        var sv = yield* this.getStateSet()
 | 
				
			||||||
        expect(sv).not.toBeUndefined();
 | 
					        expect(sv).not.toBeUndefined()
 | 
				
			||||||
        expect(sv).toEqual({
 | 
					        expect(sv).toEqual({
 | 
				
			||||||
          "1": 1,
 | 
					          '1': 1,
 | 
				
			||||||
          "2": 3
 | 
					          '2': 3
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("getOperations returns operations (no parameters)", function(done){
 | 
					    it('getOperations returns operations (no parameters)', function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        var s1 = {user: "1", clock: 55};
 | 
					        var s1 = {user: '1', clock: 55}
 | 
				
			||||||
        yield* this.setState(s1);
 | 
					        yield* this.setState(s1)
 | 
				
			||||||
        var op1 = yield* this.setOperation({
 | 
					        var op1 = yield* this.setOperation({
 | 
				
			||||||
          "id": ["1", 0],
 | 
					          'id': ['1', 0],
 | 
				
			||||||
          "stuff": true
 | 
					          'stuff': true
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        var op2 = yield* this.setOperation({
 | 
					        var op2 = yield* this.setOperation({
 | 
				
			||||||
          "id": ["1", 3],
 | 
					          'id': ['1', 3],
 | 
				
			||||||
          "stuff": true
 | 
					          'stuff': true
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        var ops = yield* this.getOperations();
 | 
					        var ops = yield* this.getOperations()
 | 
				
			||||||
        expect(ops.length).toBeGreaterThan(1);
 | 
					        expect(ops.length).toBeGreaterThan(1)
 | 
				
			||||||
        expect(ops[0]).toEqual(op1);
 | 
					        expect(ops[0]).toEqual(op1)
 | 
				
			||||||
        expect(ops[1]).toEqual(op2);
 | 
					        expect(ops[1]).toEqual(op2)
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    afterAll(function(done){
 | 
					    afterAll(function (done) {
 | 
				
			||||||
      ob.requestTransaction(function*(){
 | 
					      ob.requestTransaction(function *() {
 | 
				
			||||||
        yield* ob.removeDatabase();
 | 
					        yield* ob.removeDatabase()
 | 
				
			||||||
        ob = null;
 | 
					        ob = null
 | 
				
			||||||
        done();
 | 
					        done()
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,155 +1,144 @@
 | 
				
			|||||||
 | 
					/* global Struct, RBTree, Y */
 | 
				
			||||||
type State = {
 | 
					 | 
				
			||||||
  user: string,
 | 
					 | 
				
			||||||
  clock: number
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function copyObject (o) {
 | 
					function copyObject (o) {
 | 
				
			||||||
  var c = {};
 | 
					  var c = {}
 | 
				
			||||||
  for (var key in o) {
 | 
					  for (var key in o) {
 | 
				
			||||||
    c[key] = o[key];
 | 
					    c[key] = o[key]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return c;
 | 
					  return c
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type StateVector = Array<State>;
 | 
					Y.Memory = (function () { // eslint-disable-line no-unused-vars
 | 
				
			||||||
type StateSet = Object;
 | 
					  class Transaction extends AbstractTransaction { // eslint-disable-line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Y.Memory = (function(){ //eslint-disable-line no-unused-vars
 | 
					    constructor (store) {
 | 
				
			||||||
  class Transaction extends AbstractTransaction { //eslint-disable-line
 | 
					      super(store)
 | 
				
			||||||
    ss: StateSet;
 | 
					      this.ss = store.ss
 | 
				
			||||||
    os: RBTree;
 | 
					      this.os = store.os
 | 
				
			||||||
    store: OperationStore;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor (store : OperationStore) {
 | 
					 | 
				
			||||||
      super(store);
 | 
					 | 
				
			||||||
      this.ss = store.ss;
 | 
					 | 
				
			||||||
      this.os = store.os;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *setOperation (op) {
 | 
					    * setOperation (op) { // eslint-disable-line
 | 
				
			||||||
      // TODO: you can remove this step! probs..
 | 
					      // TODO: you can remove this step! probs..
 | 
				
			||||||
      var n = this.os.findNode(op.id);
 | 
					      var n = this.os.findNode(op.id)
 | 
				
			||||||
      n.val = op;
 | 
					      n.val = op
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *addOperation (op) {
 | 
					    * addOperation (op) { // eslint-disable-line
 | 
				
			||||||
      this.os.add(op);
 | 
					      this.os.add(op)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getOperation (id) {
 | 
					    * getOperation (id) { // eslint-disable-line
 | 
				
			||||||
      if (id == null) {
 | 
					      if (id == null) {
 | 
				
			||||||
        throw new Error("You must define id!");
 | 
					        throw new Error('You must define id!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return this.os.find(id);
 | 
					      return this.os.find(id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *removeOperation (id) {
 | 
					    * removeOperation (id) { // eslint-disable-line
 | 
				
			||||||
      this.os.delete(id);
 | 
					      this.os.delete(id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *setState (state : State) : State {
 | 
					    * setState (state) { // eslint-disable-line
 | 
				
			||||||
      this.ss[state.user] = state.clock;
 | 
					      this.ss[state.user] = state.clock
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getState (user : string) : State {
 | 
					    * getState (user) { // eslint-disable-line
 | 
				
			||||||
      var clock = this.ss[user];
 | 
					      var clock = this.ss[user]
 | 
				
			||||||
      if (clock == null){
 | 
					      if (clock == null) {
 | 
				
			||||||
        clock = 0;
 | 
					        clock = 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        user: user,
 | 
					        user: user,
 | 
				
			||||||
        clock: clock
 | 
					        clock: clock
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    *getStateVector () : StateVector {
 | 
					    }
 | 
				
			||||||
      var stateVector = [];
 | 
					    * getStateVector () { // eslint-disable-line
 | 
				
			||||||
 | 
					      var stateVector = []
 | 
				
			||||||
      for (var user in this.ss) {
 | 
					      for (var user in this.ss) {
 | 
				
			||||||
        var clock = this.ss[user];
 | 
					        var clock = this.ss[user]
 | 
				
			||||||
        stateVector.push({
 | 
					        stateVector.push({
 | 
				
			||||||
          user: user,
 | 
					          user: user,
 | 
				
			||||||
          clock: clock
 | 
					          clock: clock
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return stateVector;
 | 
					      return stateVector
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getStateSet () : StateSet {
 | 
					    * getStateSet () { // eslint-disable-line
 | 
				
			||||||
      return this.ss;
 | 
					      return this.ss
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *getOperations (startSS : StateSet) {
 | 
					    * getOperations (startSS) {
 | 
				
			||||||
      // TODO: use bounds here!
 | 
					      // TODO: use bounds here!
 | 
				
			||||||
      if (startSS == null){
 | 
					      if (startSS == null) {
 | 
				
			||||||
        startSS = {};
 | 
					        startSS = {}
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var ops = [];
 | 
					      var ops = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var endSV : StateVector = yield* this.getStateVector();
 | 
					      var endSV = yield* this.getStateVector()
 | 
				
			||||||
      for (var endState of endSV) {
 | 
					      for (var endState of endSV) {
 | 
				
			||||||
        var user = endState.user;
 | 
					        var user = endState.user
 | 
				
			||||||
        if (user === "_") {
 | 
					        if (user === '_') {
 | 
				
			||||||
          continue;
 | 
					          continue
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        var startPos = startSS[user] || 0;
 | 
					        var startPos = startSS[user] || 0
 | 
				
			||||||
        var endPos = endState.clock;
 | 
					        var endPos = endState.clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.os.iterate([user, startPos], [user, endPos], function(op){//eslint-disable-line
 | 
					        this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line
 | 
				
			||||||
          ops.push(Struct[op.struct].encode(op));
 | 
					          ops.push(Struct[op.struct].encode(op))
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var res = [];
 | 
					      var res = []
 | 
				
			||||||
      for (var op of ops) {
 | 
					      for (var op of ops) {
 | 
				
			||||||
        res.push(yield* this.makeOperationReady.call(this, startSS, op));
 | 
					        res.push(yield* this.makeOperationReady(startSS, op))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return res;
 | 
					      return res
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *makeOperationReady (ss, op) {
 | 
					    * makeOperationReady (ss, op) {
 | 
				
			||||||
      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
					      // instead of ss, you could use currSS (a ss that increments when you add an operation)
 | 
				
			||||||
      var clock;
 | 
					      var clock
 | 
				
			||||||
      var o = op;
 | 
					      var o = op
 | 
				
			||||||
      while (o.right != null){
 | 
					      while (o.right != null) {
 | 
				
			||||||
        // while unknown, go to the right
 | 
					        // while unknown, go to the right
 | 
				
			||||||
        clock = ss[o.right[0]];
 | 
					        clock = ss[o.right[0]]
 | 
				
			||||||
        if (clock != null && o.right[1] < clock) {
 | 
					        if (clock != null && o.right[1] < clock) {
 | 
				
			||||||
          break;
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        o = yield* this.getOperation(o.right);
 | 
					        o = yield* this.getOperation(o.right)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      op = copyObject(op);
 | 
					      op = copyObject(op)
 | 
				
			||||||
      op.right = o.right;
 | 
					      op.right = o.right
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
 | 
					  class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
 | 
				
			||||||
    constructor (y) {
 | 
					    constructor (y) {
 | 
				
			||||||
      super(y);
 | 
					      super(y)
 | 
				
			||||||
      this.os = new RBTree();
 | 
					      this.os = new RBTree()
 | 
				
			||||||
      this.ss = {};
 | 
					      this.ss = {}
 | 
				
			||||||
      this.waitingTransactions = [];
 | 
					      this.waitingTransactions = []
 | 
				
			||||||
      this.transactionInProgress = false;
 | 
					      this.transactionInProgress = false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    requestTransaction (_makeGen : Function) {
 | 
					    requestTransaction (_makeGen) {
 | 
				
			||||||
      if (!this.transactionInProgress) {
 | 
					      if (!this.transactionInProgress) {
 | 
				
			||||||
        this.transactionInProgress = true;
 | 
					        this.transactionInProgress = true
 | 
				
			||||||
        setTimeout(() => {
 | 
					        setTimeout(() => {
 | 
				
			||||||
          var makeGen = _makeGen;
 | 
					          var makeGen = _makeGen
 | 
				
			||||||
          while (makeGen != null) {
 | 
					          while (makeGen != null) {
 | 
				
			||||||
            var t = new Transaction(this);
 | 
					            var t = new Transaction(this)
 | 
				
			||||||
            var gen = makeGen.call(t);
 | 
					            var gen = makeGen.call(t)
 | 
				
			||||||
            var res = gen.next();
 | 
					            var res = gen.next()
 | 
				
			||||||
            while(!res.done){
 | 
					            while (!res.done) {
 | 
				
			||||||
              if (res.value === "transaction") {
 | 
					              if (res.value === 'transaction') {
 | 
				
			||||||
                res = gen.next(t);
 | 
					                res = gen.next(t)
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)");
 | 
					                throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)")
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            makeGen = this.waitingTransactions.shift();
 | 
					            makeGen = this.waitingTransactions.shift()
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          this.transactionInProgress = false;
 | 
					          this.transactionInProgress = false
 | 
				
			||||||
        }, 0);
 | 
					        }, 0)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.waitingTransactions.push(_makeGen);
 | 
					        this.waitingTransactions.push(_makeGen)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *removeDatabase () {
 | 
					    * removeDatabase () { // eslint-disable-line
 | 
				
			||||||
      delete this.os;
 | 
					      delete this.os
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return OperationStore;
 | 
					  return OperationStore
 | 
				
			||||||
})();
 | 
					})()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,367 +1,367 @@
 | 
				
			|||||||
 | 
					/* global compareIds */
 | 
				
			||||||
function smaller (a, b) {
 | 
					function smaller (a, b) {
 | 
				
			||||||
  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]);
 | 
					  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class N {
 | 
					class N {
 | 
				
			||||||
  // A created node is always red!
 | 
					  // A created node is always red!
 | 
				
			||||||
  constructor (val) {
 | 
					  constructor (val) {
 | 
				
			||||||
    this.val = val;
 | 
					    this.val = val
 | 
				
			||||||
    this.color = true;
 | 
					    this.color = true
 | 
				
			||||||
    this._left = null;
 | 
					    this._left = null
 | 
				
			||||||
    this._right = null;
 | 
					    this._right = null
 | 
				
			||||||
    this._parent = null;
 | 
					    this._parent = null
 | 
				
			||||||
    if (val.id === null) {
 | 
					    if (val.id === null) {
 | 
				
			||||||
      throw new Error("You must define id!");
 | 
					      throw new Error('You must define id!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  isRed () { return this.color; }
 | 
					  isRed () { return this.color }
 | 
				
			||||||
  isBlack () { return !this.color; }
 | 
					  isBlack () { return !this.color }
 | 
				
			||||||
  redden () { this.color = true; return this; }
 | 
					  redden () { this.color = true; return this }
 | 
				
			||||||
  blacken () { this.color = false; return this; }
 | 
					  blacken () { this.color = false; return this }
 | 
				
			||||||
  get grandparent () {
 | 
					  get grandparent () {
 | 
				
			||||||
    return this.parent.parent;
 | 
					    return this.parent.parent
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  get parent () {
 | 
					  get parent () {
 | 
				
			||||||
    return this._parent;
 | 
					    return this._parent
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  get sibling () {
 | 
					  get sibling () {
 | 
				
			||||||
    return (this === this.parent.left) ?
 | 
					    return (this === this.parent.left) ?
 | 
				
			||||||
            this.parent.right : this.parent.left;
 | 
					      this.parent.right : this.parent.left
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  get left () {
 | 
					  get left () {
 | 
				
			||||||
    return this._left;
 | 
					    return this._left
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  get right () {
 | 
					  get right () {
 | 
				
			||||||
    return this._right;
 | 
					    return this._right
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  set left (n) {
 | 
					  set left (n) {
 | 
				
			||||||
    if (n !== null) {
 | 
					    if (n !== null) {
 | 
				
			||||||
      n._parent = this;
 | 
					      n._parent = this
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this._left = n;
 | 
					    this._left = n
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  set right (n) {
 | 
					  set right (n) {
 | 
				
			||||||
    if (n !== null) {
 | 
					    if (n !== null) {
 | 
				
			||||||
      n._parent = this;
 | 
					      n._parent = this
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this._right = n;
 | 
					    this._right = n
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  rotateLeft (tree) {
 | 
					  rotateLeft (tree) {
 | 
				
			||||||
    var parent = this.parent;
 | 
					    var parent = this.parent
 | 
				
			||||||
    var newParent = this.right;
 | 
					    var newParent = this.right
 | 
				
			||||||
    var newRight = this.right.left;
 | 
					    var newRight = this.right.left
 | 
				
			||||||
    newParent.left = this;
 | 
					    newParent.left = this
 | 
				
			||||||
    this.right = newRight;
 | 
					    this.right = newRight
 | 
				
			||||||
    if (parent === null) {
 | 
					    if (parent === null) {
 | 
				
			||||||
      tree.root = newParent;
 | 
					      tree.root = newParent
 | 
				
			||||||
      newParent._parent = null;
 | 
					      newParent._parent = null
 | 
				
			||||||
    } else if (parent.left === this) {
 | 
					    } else if (parent.left === this) {
 | 
				
			||||||
      parent.left = newParent;
 | 
					      parent.left = newParent
 | 
				
			||||||
    } else if (parent.right === this) {
 | 
					    } else if (parent.right === this) {
 | 
				
			||||||
      parent.right = newParent;
 | 
					      parent.right = newParent
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw new Error("The elements are wrongly connected!");
 | 
					      throw new Error('The elements are wrongly connected!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  next () {
 | 
					  next () {
 | 
				
			||||||
    if ( this.right !== null ) {
 | 
					    if (this.right !== null) {
 | 
				
			||||||
      // search the most left node in the right tree
 | 
					      // search the most left node in the right tree
 | 
				
			||||||
      var o = this.right;
 | 
					      var o = this.right
 | 
				
			||||||
      while (o.left !== null) {
 | 
					      while (o.left !== null) {
 | 
				
			||||||
        o = o.left;
 | 
					        o = o.left
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return o;
 | 
					      return o
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      var p = this;
 | 
					      var p = this
 | 
				
			||||||
      while (p.parent !== null && p !== p.parent.left) {
 | 
					      while (p.parent !== null && p !== p.parent.left) {
 | 
				
			||||||
        p = p.parent;
 | 
					        p = p.parent
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return p.parent;
 | 
					      return p.parent
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  rotateRight (tree) {
 | 
					  rotateRight (tree) {
 | 
				
			||||||
    var parent = this.parent;
 | 
					    var parent = this.parent
 | 
				
			||||||
    var newParent = this.left;
 | 
					    var newParent = this.left
 | 
				
			||||||
    var newLeft = this.left.right;
 | 
					    var newLeft = this.left.right
 | 
				
			||||||
    newParent.right = this;
 | 
					    newParent.right = this
 | 
				
			||||||
    this.left = newLeft;
 | 
					    this.left = newLeft
 | 
				
			||||||
    if (parent === null) {
 | 
					    if (parent === null) {
 | 
				
			||||||
      tree.root = newParent;
 | 
					      tree.root = newParent
 | 
				
			||||||
      newParent._parent = null;
 | 
					      newParent._parent = null
 | 
				
			||||||
    } else if (parent.left === this) {
 | 
					    } else if (parent.left === this) {
 | 
				
			||||||
      parent.left = newParent;
 | 
					      parent.left = newParent
 | 
				
			||||||
    } else if (parent.right === this) {
 | 
					    } else if (parent.right === this) {
 | 
				
			||||||
      parent.right = newParent;
 | 
					      parent.right = newParent
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw new Error("The elements are wrongly connected!");
 | 
					      throw new Error('The elements are wrongly connected!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  getUncle () {
 | 
					  getUncle () {
 | 
				
			||||||
    // we can assume that grandparent exists when this is called!
 | 
					    // we can assume that grandparent exists when this is called!
 | 
				
			||||||
    if (this.parent === this.parent.parent.left) {
 | 
					    if (this.parent === this.parent.parent.left) {
 | 
				
			||||||
      return this.parent.parent.right;
 | 
					      return this.parent.parent.right
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return this.parent.parent.left;
 | 
					      return this.parent.parent.left
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RBTree { //eslint-disable-line no-unused-vars
 | 
					class RBTree { // eslint-disable-line no-unused-vars
 | 
				
			||||||
  constructor () {
 | 
					  constructor () {
 | 
				
			||||||
    this.root = null;
 | 
					    this.root = null
 | 
				
			||||||
    this.length = 0;
 | 
					    this.length = 0
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  findNodeWithLowerBound (from) {
 | 
					  findNodeWithLowerBound (from) {
 | 
				
			||||||
    if (from === void 0) {
 | 
					    if (from === void 0) {
 | 
				
			||||||
      throw new Error("You must define from!");
 | 
					      throw new Error('You must define from!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var o = this.root;
 | 
					    var o = this.root
 | 
				
			||||||
    if (o === null) {
 | 
					    if (o === null) {
 | 
				
			||||||
      return false;
 | 
					      return false
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
 | 
					        if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
 | 
				
			||||||
          // o is included in the bound
 | 
					          // o is included in the bound
 | 
				
			||||||
          // try to find an element that is closer to the bound
 | 
					          // try to find an element that is closer to the bound
 | 
				
			||||||
          o = o.left;
 | 
					          o = o.left
 | 
				
			||||||
        } else if (from !== null && smaller(o.val.id, from)) {
 | 
					        } else if (from !== null && smaller(o.val.id, from)) {
 | 
				
			||||||
          // o is not within the bound, maybe one of the right elements is..
 | 
					          // o is not within the bound, maybe one of the right elements is..
 | 
				
			||||||
          if (o.right !== null) {
 | 
					          if (o.right !== null) {
 | 
				
			||||||
            o = o.right;
 | 
					            o = o.right
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            // there is no right element. Search for the next bigger element,
 | 
					            // there is no right element. Search for the next bigger element,
 | 
				
			||||||
            // this should be within the bounds
 | 
					            // this should be within the bounds
 | 
				
			||||||
            return o.next();
 | 
					            return o.next()
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return o;
 | 
					          return o
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  iterate (from, to, f) {
 | 
					  iterate (from, to, f) {
 | 
				
			||||||
    var o = this.findNodeWithLowerBound(from);
 | 
					    var o = this.findNodeWithLowerBound(from)
 | 
				
			||||||
    while ( o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to)) ) {
 | 
					    while (o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to))) {
 | 
				
			||||||
      f(o.val);
 | 
					      f(o.val)
 | 
				
			||||||
      o = o.next();
 | 
					      o = o.next()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return true;
 | 
					    return true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  find (id) {
 | 
					  find (id) {
 | 
				
			||||||
    return this.findNode(id).val;
 | 
					    return this.findNode(id).val
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  findNode (id) {
 | 
					  findNode (id) {
 | 
				
			||||||
    if (id == null || id.constructor !== Array) {
 | 
					    if (id == null || id.constructor !== Array) {
 | 
				
			||||||
      throw new Error("Expect id to be an array!");
 | 
					      throw new Error('Expect id to be an array!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var o = this.root;
 | 
					    var o = this.root
 | 
				
			||||||
    if (o === null) {
 | 
					    if (o === null) {
 | 
				
			||||||
      return false;
 | 
					      return false
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if (o === null) {
 | 
					        if (o === null) {
 | 
				
			||||||
          return false;
 | 
					          return false
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (smaller(id, o.val.id)) {
 | 
					        if (smaller(id, o.val.id)) {
 | 
				
			||||||
          o = o.left;
 | 
					          o = o.left
 | 
				
			||||||
        } else if (smaller(o.val.id, id)) {
 | 
					        } else if (smaller(o.val.id, id)) {
 | 
				
			||||||
          o = o.right;
 | 
					          o = o.right
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return o;
 | 
					          return o
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  delete (id) {
 | 
					  delete (id) {
 | 
				
			||||||
    if (id == null || id.constructor !== Array) {
 | 
					    if (id == null || id.constructor !== Array) {
 | 
				
			||||||
      throw new Error("id is expected to be an Array!");
 | 
					      throw new Error('id is expected to be an Array!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var d = this.findNode(id);
 | 
					    var d = this.findNode(id)
 | 
				
			||||||
    if (d == null) {
 | 
					    if (d == null) {
 | 
				
			||||||
      throw new Error("Element does not exist!");
 | 
					      throw new Error('Element does not exist!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.length--;
 | 
					    this.length--
 | 
				
			||||||
    if (d.left !== null && d.right !== null) {
 | 
					    if (d.left !== null && d.right !== null) {
 | 
				
			||||||
      // switch d with the greates element in the left subtree.
 | 
					      // switch d with the greates element in the left subtree.
 | 
				
			||||||
      // o should have at most one child.
 | 
					      // o should have at most one child.
 | 
				
			||||||
      var o = d.left;
 | 
					      var o = d.left
 | 
				
			||||||
      // find
 | 
					      // find
 | 
				
			||||||
      while (o.right !== null) {
 | 
					      while (o.right !== null) {
 | 
				
			||||||
        o = o.right;
 | 
					        o = o.right
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // switch
 | 
					      // switch
 | 
				
			||||||
      d.val = o.val;
 | 
					      d.val = o.val
 | 
				
			||||||
      d = o;
 | 
					      d = o
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // d has at most one child
 | 
					    // d has at most one child
 | 
				
			||||||
    // let n be the node that replaces d
 | 
					    // let n be the node that replaces d
 | 
				
			||||||
    var isFakeChild;
 | 
					    var isFakeChild
 | 
				
			||||||
    var child = d.left || d.right;
 | 
					    var child = d.left || d.right
 | 
				
			||||||
    if ( child === null) {
 | 
					    if (child === null) {
 | 
				
			||||||
      isFakeChild = true;
 | 
					      isFakeChild = true
 | 
				
			||||||
      child = new N({id: 0});
 | 
					      child = new N({id: 0})
 | 
				
			||||||
      child.blacken();
 | 
					      child.blacken()
 | 
				
			||||||
      d.right = child;
 | 
					      d.right = child
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      isFakeChild = false;
 | 
					      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 {
 | 
					      } else {
 | 
				
			||||||
        this.root = null;
 | 
					        this.root = null
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    } else if (d.parent.left === d) {
 | 
					    } else if (d.parent.left === d) {
 | 
				
			||||||
      d.parent.left = child;
 | 
					      d.parent.left = child
 | 
				
			||||||
    } else if (d.parent.right === d) {
 | 
					    } else if (d.parent.right === d) {
 | 
				
			||||||
      d.parent.right = child;
 | 
					      d.parent.right = child
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw new Error("Impossible!");
 | 
					      throw new Error('Impossible!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if ( d.isBlack() ) {
 | 
					    if (d.isBlack()) {
 | 
				
			||||||
      if ( child.isRed() ) {
 | 
					      if (child.isRed()) {
 | 
				
			||||||
        child.blacken();
 | 
					        child.blacken()
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this._fixDelete(child);
 | 
					        this._fixDelete(child)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.root.blacken();
 | 
					    this.root.blacken()
 | 
				
			||||||
    if (isFakeChild) {
 | 
					    if (isFakeChild) {
 | 
				
			||||||
      if (child.parent.left === child) {
 | 
					      if (child.parent.left === child) {
 | 
				
			||||||
        child.parent.left = null;
 | 
					        child.parent.left = null
 | 
				
			||||||
      } else if (child.parent.right === child) {
 | 
					      } else if (child.parent.right === child) {
 | 
				
			||||||
        child.parent.right = null;
 | 
					        child.parent.right = null
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        throw new Error("Impossible #3");
 | 
					        throw new Error('Impossible #3')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  _fixDelete (n) {
 | 
					  _fixDelete (n) {
 | 
				
			||||||
    function isBlack (node) {
 | 
					    function isBlack (node) {
 | 
				
			||||||
      return node !== null ? node.isBlack() : true;
 | 
					      return node !== null ? node.isBlack() : true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    function isRed(node) {
 | 
					    function isRed (node) {
 | 
				
			||||||
      return node !== null ? node.isRed() : false;
 | 
					      return node !== null ? node.isRed() : false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (n.parent === null) {
 | 
					    if (n.parent === null) {
 | 
				
			||||||
      // this can only be called after the first iteration of fixDelete.
 | 
					      // this can only be called after the first iteration of fixDelete.
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // d was already replaced by the child
 | 
					    // d was already replaced by the child
 | 
				
			||||||
    // d is not the root
 | 
					    // d is not the root
 | 
				
			||||||
    // d and child are black
 | 
					    // d and child are black
 | 
				
			||||||
    var sibling = n.sibling;
 | 
					    var sibling = n.sibling
 | 
				
			||||||
    if (isRed(sibling)) {
 | 
					    if (isRed(sibling)) {
 | 
				
			||||||
      // make sibling the grandfather
 | 
					      // make sibling the grandfather
 | 
				
			||||||
      n.parent.redden();
 | 
					      n.parent.redden()
 | 
				
			||||||
      sibling.blacken();
 | 
					      sibling.blacken()
 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					      if (n === n.parent.left) {
 | 
				
			||||||
        n.parent.rotateLeft(this);
 | 
					        n.parent.rotateLeft(this)
 | 
				
			||||||
      } else if (n === n.parent.right) {
 | 
					      } else if (n === n.parent.right) {
 | 
				
			||||||
        n.parent.rotateRight(this);
 | 
					        n.parent.rotateRight(this)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        throw new Error("Impossible #2");
 | 
					        throw new Error('Impossible #2')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      sibling = n.sibling;
 | 
					      sibling = n.sibling
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // parent, sibling, and children of n are black
 | 
					    // parent, sibling, and children of n are black
 | 
				
			||||||
    if ( n.parent.isBlack() &&
 | 
					    if (n.parent.isBlack() &&
 | 
				
			||||||
      sibling.isBlack() &&
 | 
					      sibling.isBlack() &&
 | 
				
			||||||
      isBlack(sibling.left) &&
 | 
					      isBlack(sibling.left) &&
 | 
				
			||||||
      isBlack(sibling.right)
 | 
					      isBlack(sibling.right)
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      sibling.redden();
 | 
					      sibling.redden()
 | 
				
			||||||
      this._fixDelete(n.parent);
 | 
					      this._fixDelete(n.parent)
 | 
				
			||||||
    } else if ( n.parent.isRed() &&
 | 
					    } else if (n.parent.isRed() &&
 | 
				
			||||||
      sibling.isBlack() &&
 | 
					      sibling.isBlack() &&
 | 
				
			||||||
      isBlack(sibling.left) &&
 | 
					      isBlack(sibling.left) &&
 | 
				
			||||||
      isBlack(sibling.right)
 | 
					      isBlack(sibling.right)
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      sibling.redden();
 | 
					      sibling.redden()
 | 
				
			||||||
      n.parent.blacken();
 | 
					      n.parent.blacken()
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if ( n === n.parent.left &&
 | 
					      if (n === n.parent.left &&
 | 
				
			||||||
        sibling.isBlack() &&
 | 
					        sibling.isBlack() &&
 | 
				
			||||||
        isRed(sibling.left) &&
 | 
					        isRed(sibling.left) &&
 | 
				
			||||||
        isBlack(sibling.right)
 | 
					        isBlack(sibling.right)
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        sibling.redden();
 | 
					        sibling.redden()
 | 
				
			||||||
        sibling.left.blacken();
 | 
					        sibling.left.blacken()
 | 
				
			||||||
        sibling.rotateRight(this);
 | 
					        sibling.rotateRight(this)
 | 
				
			||||||
        sibling = n.sibling;
 | 
					        sibling = n.sibling
 | 
				
			||||||
      } else if ( n === n.parent.right &&
 | 
					      } else if (n === n.parent.right &&
 | 
				
			||||||
        sibling.isBlack() &&
 | 
					        sibling.isBlack() &&
 | 
				
			||||||
        isRed(sibling.right) &&
 | 
					        isRed(sibling.right) &&
 | 
				
			||||||
        isBlack(sibling.left)
 | 
					        isBlack(sibling.left)
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        sibling.redden();
 | 
					        sibling.redden()
 | 
				
			||||||
        sibling.right.blacken();
 | 
					        sibling.right.blacken()
 | 
				
			||||||
        sibling.rotateLeft(this);
 | 
					        sibling.rotateLeft(this)
 | 
				
			||||||
        sibling = n.sibling;
 | 
					        sibling = n.sibling
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      sibling.color = n.parent.color;
 | 
					      sibling.color = n.parent.color
 | 
				
			||||||
      n.parent.blacken();
 | 
					      n.parent.blacken()
 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					      if (n === n.parent.left) {
 | 
				
			||||||
        sibling.right.blacken();
 | 
					        sibling.right.blacken()
 | 
				
			||||||
        n.parent.rotateLeft(this);
 | 
					        n.parent.rotateLeft(this)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        sibling.left.blacken();
 | 
					        sibling.left.blacken()
 | 
				
			||||||
        n.parent.rotateRight(this);
 | 
					        n.parent.rotateRight(this)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  add (v) {
 | 
					  add (v) {
 | 
				
			||||||
    if (v == null || v.id == null || v.id.constructor !== Array) {
 | 
					    if (v == null || v.id == null || v.id.constructor !== Array) {
 | 
				
			||||||
      throw new Error("v is expected to have an id property which is an Array!");
 | 
					      throw new Error('v is expected to have an id property which is an Array!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var node = new N(v);
 | 
					    var node = new N(v)
 | 
				
			||||||
    if (this.root !== null) {
 | 
					    if (this.root !== null) {
 | 
				
			||||||
      var p = this.root; // p abbrev. parent
 | 
					      var p = this.root // p abbrev. parent
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if (smaller(node.val.id, p.val.id)) {
 | 
					        if (smaller(node.val.id, p.val.id)) {
 | 
				
			||||||
          if (p.left === null) {
 | 
					          if (p.left === null) {
 | 
				
			||||||
            p.left = node;
 | 
					            p.left = node
 | 
				
			||||||
            break;
 | 
					            break
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            p = p.left;
 | 
					            p = p.left
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if (smaller(p.val.id, node.val.id)) {
 | 
					        } else if (smaller(p.val.id, node.val.id)) {
 | 
				
			||||||
          if (p.right === null) {
 | 
					          if (p.right === null) {
 | 
				
			||||||
            p.right = node;
 | 
					            p.right = node
 | 
				
			||||||
            break;
 | 
					            break
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            p = p.right;
 | 
					            p = p.right
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return false;
 | 
					          return false
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this._fixInsert(node);
 | 
					      this._fixInsert(node)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.root = node;
 | 
					      this.root = node
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.length++;
 | 
					    this.length++
 | 
				
			||||||
    this.root.blacken();
 | 
					    this.root.blacken()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  _fixInsert (n) {
 | 
					  _fixInsert (n) {
 | 
				
			||||||
    if (n.parent === null) {
 | 
					    if (n.parent === null) {
 | 
				
			||||||
      n.blacken();
 | 
					      n.blacken()
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    } else if (n.parent.isBlack()) {
 | 
					    } else if (n.parent.isBlack()) {
 | 
				
			||||||
      return;
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var uncle = n.getUncle();
 | 
					    var uncle = n.getUncle()
 | 
				
			||||||
    if (uncle !== null && uncle.isRed()) {
 | 
					    if (uncle !== null && uncle.isRed()) {
 | 
				
			||||||
      // Note: parent: red, uncle: red
 | 
					      // Note: parent: red, uncle: red
 | 
				
			||||||
      n.parent.blacken();
 | 
					      n.parent.blacken()
 | 
				
			||||||
      uncle.blacken();
 | 
					      uncle.blacken()
 | 
				
			||||||
      n.grandparent.redden();
 | 
					      n.grandparent.redden()
 | 
				
			||||||
      this._fixInsert(n.grandparent);
 | 
					      this._fixInsert(n.grandparent)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // Note: parent: red, uncle: black or null
 | 
					      // Note: parent: red, uncle: black or null
 | 
				
			||||||
      // Now we transform the tree in such a way that
 | 
					      // Now we transform the tree in such a way that
 | 
				
			||||||
@ -370,30 +370,28 @@ class RBTree { //eslint-disable-line no-unused-vars
 | 
				
			|||||||
      //     and grandparent.left.left.isRed
 | 
					      //     and grandparent.left.left.isRed
 | 
				
			||||||
      //   2) grandparent.right.isRed
 | 
					      //   2) grandparent.right.isRed
 | 
				
			||||||
      //     and grandparent.right.right.isRed
 | 
					      //     and grandparent.right.right.isRed
 | 
				
			||||||
      if (n === n.parent.right
 | 
					      if (n === n.parent.right && n.parent === n.grandparent.left) {
 | 
				
			||||||
        && n.parent === n.grandparent.left) {
 | 
					        n.parent.rotateLeft(this)
 | 
				
			||||||
          n.parent.rotateLeft(this);
 | 
					 | 
				
			||||||
        // Since we rotated and want to use the previous
 | 
					        // Since we rotated and want to use the previous
 | 
				
			||||||
        // cases, we need to set n in such a way that
 | 
					        // cases, we need to set n in such a way that
 | 
				
			||||||
        // n.parent.isRed again
 | 
					        // n.parent.isRed again
 | 
				
			||||||
          n = n.left;
 | 
					        n = n.left
 | 
				
			||||||
      } else if (n === n.parent.left
 | 
					      } else if (n === n.parent.left && n.parent === n.grandparent.right) {
 | 
				
			||||||
        && n.parent === n.grandparent.right) {
 | 
					        n.parent.rotateRight(this)
 | 
				
			||||||
          n.parent.rotateRight(this);
 | 
					 | 
				
			||||||
        // see above
 | 
					        // see above
 | 
				
			||||||
          n = n.right;
 | 
					        n = n.right
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // Case 1) or 2) hold from here on.
 | 
					      // Case 1) or 2) hold from here on.
 | 
				
			||||||
      // Now traverse grandparent, make parent a black node
 | 
					      // Now traverse grandparent, make parent a black node
 | 
				
			||||||
      // on the highest level which holds two red nodes.
 | 
					      // on the highest level which holds two red nodes.
 | 
				
			||||||
      n.parent.blacken();
 | 
					      n.parent.blacken()
 | 
				
			||||||
      n.grandparent.redden();
 | 
					      n.grandparent.redden()
 | 
				
			||||||
      if (n === n.parent.left) {
 | 
					      if (n === n.parent.left) {
 | 
				
			||||||
        // Case 1
 | 
					        // Case 1
 | 
				
			||||||
        n.grandparent.rotateRight(this);
 | 
					        n.grandparent.rotateRight(this)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // Case 2
 | 
					        // Case 2
 | 
				
			||||||
        n.grandparent.rotateLeft(this);
 | 
					        n.grandparent.rotateLeft(this)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,209 +1,209 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global RBTree, smaller, compareIds */
 | 
				
			||||||
/*eslint-env browser,jasmine,console */
 | 
					/* eslint-env browser,jasmine,console */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var numberOfRBTreeTests = 1000;
 | 
					var numberOfRBTreeTests = 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function itRedNodesDoNotHaveBlackChildren (tree) {
 | 
					function itRedNodesDoNotHaveBlackChildren (tree) {
 | 
				
			||||||
  it("Red nodes do not have black children", function(){
 | 
					  it('Red nodes do not have black children', function () {
 | 
				
			||||||
    function traverse (n) {
 | 
					    function traverse (n) {
 | 
				
			||||||
      if (n == null) {
 | 
					      if (n == null) {
 | 
				
			||||||
        return;
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (n.isRed()) {
 | 
					      if (n.isRed()) {
 | 
				
			||||||
        if (n.left != null) {
 | 
					        if (n.left != null) {
 | 
				
			||||||
          expect(n.left.isRed()).not.toBeTruthy();
 | 
					          expect(n.left.isRed()).not.toBeTruthy()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (n.right != null) {
 | 
					        if (n.right != null) {
 | 
				
			||||||
          expect(n.right.isRed()).not.toBeTruthy();
 | 
					          expect(n.right.isRed()).not.toBeTruthy()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      traverse(n.left);
 | 
					      traverse(n.left)
 | 
				
			||||||
      traverse(n.right);
 | 
					      traverse(n.right)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    traverse(tree.root);
 | 
					    traverse(tree.root)
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function itBlackHeightOfSubTreesAreEqual (tree){
 | 
					function itBlackHeightOfSubTreesAreEqual (tree) {
 | 
				
			||||||
  it("Black-height of sub-trees are equal", function(){
 | 
					  it('Black-height of sub-trees are equal', function () {
 | 
				
			||||||
    function traverse (n) {
 | 
					    function traverse (n) {
 | 
				
			||||||
      if (n == null) {
 | 
					      if (n == null) {
 | 
				
			||||||
        return 0;
 | 
					        return 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var sub1 = traverse(n.left);
 | 
					      var sub1 = traverse(n.left)
 | 
				
			||||||
      var sub2 = traverse(n.right);
 | 
					      var sub2 = traverse(n.right)
 | 
				
			||||||
      expect(sub1).toEqual(sub2);
 | 
					      expect(sub1).toEqual(sub2)
 | 
				
			||||||
      if(n.isRed()) {
 | 
					      if (n.isRed()) {
 | 
				
			||||||
        return sub1;
 | 
					        return sub1
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        return sub1 + 1;
 | 
					        return sub1 + 1
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    traverse(tree.root);
 | 
					    traverse(tree.root)
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function itRootNodeIsBlack(tree) {
 | 
					function itRootNodeIsBlack (tree) {
 | 
				
			||||||
  it("root node is black", function(){
 | 
					  it('root node is black', function () {
 | 
				
			||||||
    expect(tree.root == null || tree.root.isBlack()).toBeTruthy();
 | 
					    expect(tree.root == null || tree.root.isBlack()).toBeTruthy()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("RedBlack Tree", function(){
 | 
					describe('RedBlack Tree', function () {
 | 
				
			||||||
  beforeEach(function(){
 | 
					  beforeEach(function () {
 | 
				
			||||||
    this.tree = new RBTree();
 | 
					    this.tree = new RBTree()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  it("can add&retrieve 5 elements", function(){
 | 
					  it('can add&retrieve 5 elements', function () {
 | 
				
			||||||
    this.tree.add({val: "four", id: [4]});
 | 
					    this.tree.add({val: 'four', id: [4]})
 | 
				
			||||||
    this.tree.add({val: "one", id: [1]});
 | 
					    this.tree.add({val: 'one', id: [1]})
 | 
				
			||||||
    this.tree.add({val: "three", id: [3]});
 | 
					    this.tree.add({val: 'three', id: [3]})
 | 
				
			||||||
    this.tree.add({val: "two", id: [2]});
 | 
					    this.tree.add({val: 'two', id: [2]})
 | 
				
			||||||
    this.tree.add({val: "five", id: [5]});
 | 
					    this.tree.add({val: 'five', id: [5]})
 | 
				
			||||||
    expect(this.tree.find([1]).val).toEqual("one");
 | 
					    expect(this.tree.find([1]).val).toEqual('one')
 | 
				
			||||||
    expect(this.tree.find([2]).val).toEqual("two");
 | 
					    expect(this.tree.find([2]).val).toEqual('two')
 | 
				
			||||||
    expect(this.tree.find([3]).val).toEqual("three");
 | 
					    expect(this.tree.find([3]).val).toEqual('three')
 | 
				
			||||||
    expect(this.tree.find([4]).val).toEqual("four");
 | 
					    expect(this.tree.find([4]).val).toEqual('four')
 | 
				
			||||||
    expect(this.tree.find([5]).val).toEqual("five");
 | 
					    expect(this.tree.find([5]).val).toEqual('five')
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it("5 elements do not exist anymore after deleting them", function(){
 | 
					  it('5 elements do not exist anymore after deleting them', function () {
 | 
				
			||||||
    this.tree.add({val: "four", id: [4]});
 | 
					    this.tree.add({val: 'four', id: [4]})
 | 
				
			||||||
    this.tree.add({val: "one", id: [1]});
 | 
					    this.tree.add({val: 'one', id: [1]})
 | 
				
			||||||
    this.tree.add({val: "three", id: [3]});
 | 
					    this.tree.add({val: 'three', id: [3]})
 | 
				
			||||||
    this.tree.add({val: "two", id: [2]});
 | 
					    this.tree.add({val: 'two', id: [2]})
 | 
				
			||||||
    this.tree.add({val: "five", id: [5]});
 | 
					    this.tree.add({val: 'five', id: [5]})
 | 
				
			||||||
    this.tree.delete([4]);
 | 
					    this.tree.delete([4])
 | 
				
			||||||
    expect(this.tree.find([4])).not.toBeTruthy();
 | 
					    expect(this.tree.find([4])).not.toBeTruthy()
 | 
				
			||||||
    this.tree.delete([3]);
 | 
					    this.tree.delete([3])
 | 
				
			||||||
    expect(this.tree.find([3])).not.toBeTruthy();
 | 
					    expect(this.tree.find([3])).not.toBeTruthy()
 | 
				
			||||||
    this.tree.delete([2]);
 | 
					    this.tree.delete([2])
 | 
				
			||||||
    expect(this.tree.find([2])).not.toBeTruthy();
 | 
					    expect(this.tree.find([2])).not.toBeTruthy()
 | 
				
			||||||
    this.tree.delete([1]);
 | 
					    this.tree.delete([1])
 | 
				
			||||||
    expect(this.tree.find([1])).not.toBeTruthy();
 | 
					    expect(this.tree.find([1])).not.toBeTruthy()
 | 
				
			||||||
    this.tree.delete([5]);
 | 
					    this.tree.delete([5])
 | 
				
			||||||
    expect(this.tree.find([5])).not.toBeTruthy();
 | 
					    expect(this.tree.find([5])).not.toBeTruthy()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it("debug #1", function(){
 | 
					  it('debug #1', function () {
 | 
				
			||||||
    this.tree.add({id: [2]});
 | 
					    this.tree.add({id: [2]})
 | 
				
			||||||
    this.tree.add({id: [0]});
 | 
					    this.tree.add({id: [0]})
 | 
				
			||||||
    this.tree.delete([2]);
 | 
					    this.tree.delete([2])
 | 
				
			||||||
    this.tree.add({id: [1]});
 | 
					    this.tree.add({id: [1]})
 | 
				
			||||||
    expect(this.tree.find([0])).not.toBeUndefined();
 | 
					    expect(this.tree.find([0])).not.toBeUndefined()
 | 
				
			||||||
    expect(this.tree.find([1])).not.toBeUndefined();
 | 
					    expect(this.tree.find([1])).not.toBeUndefined()
 | 
				
			||||||
    expect(this.tree.find([2])).toBeUndefined();
 | 
					    expect(this.tree.find([2])).toBeUndefined()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  describe("debug #2", function(){
 | 
					  describe('debug #2', function () {
 | 
				
			||||||
    var tree = new RBTree();
 | 
					    var tree = new RBTree()
 | 
				
			||||||
    tree.add({id: [8433]});
 | 
					    tree.add({id: [8433]})
 | 
				
			||||||
    tree.add({id: [12844]});
 | 
					    tree.add({id: [12844]})
 | 
				
			||||||
    tree.add({id: [1795]});
 | 
					    tree.add({id: [1795]})
 | 
				
			||||||
    tree.add({id: [30302]});
 | 
					    tree.add({id: [30302]})
 | 
				
			||||||
    tree.add({id: [64287]});
 | 
					    tree.add({id: [64287]})
 | 
				
			||||||
    tree.delete([8433]);
 | 
					    tree.delete([8433])
 | 
				
			||||||
    tree.add({id: [28996]});
 | 
					    tree.add({id: [28996]})
 | 
				
			||||||
    tree.delete([64287]);
 | 
					    tree.delete([64287])
 | 
				
			||||||
    tree.add({id: [22721]});
 | 
					    tree.add({id: [22721]})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    itRootNodeIsBlack(tree, []);
 | 
					    itRootNodeIsBlack(tree, [])
 | 
				
			||||||
    itBlackHeightOfSubTreesAreEqual(tree, []);
 | 
					    itBlackHeightOfSubTreesAreEqual(tree, [])
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe(`After adding&deleting (0.8/0.2) ${numberOfRBTreeTests} times`, function () {
 | 
					  describe(`After adding&deleting (0.8/0.2) ${numberOfRBTreeTests} times`, function () {
 | 
				
			||||||
    var elements = [];
 | 
					    var elements = []
 | 
				
			||||||
    var tree = new RBTree();
 | 
					    var tree = new RBTree()
 | 
				
			||||||
    for(var i = 0; i < numberOfRBTreeTests; i++) {
 | 
					    for (var i = 0; i < numberOfRBTreeTests; i++) {
 | 
				
			||||||
      var r = Math.random();
 | 
					      var r = Math.random()
 | 
				
			||||||
      if (r < 0.8) {
 | 
					      if (r < 0.8) {
 | 
				
			||||||
        var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)];
 | 
					        var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)]
 | 
				
			||||||
        elements.push(obj);
 | 
					        elements.push(obj)
 | 
				
			||||||
        tree.add({id: obj});
 | 
					        tree.add({id: obj})
 | 
				
			||||||
      } else if (elements.length > 0) {
 | 
					      } else if (elements.length > 0) {
 | 
				
			||||||
        var elemid = Math.floor(Math.random() * elements.length);
 | 
					        var elemid = Math.floor(Math.random() * elements.length)
 | 
				
			||||||
        var elem = elements[elemid];
 | 
					        var elem = elements[elemid]
 | 
				
			||||||
        elements = elements.filter(function(e){return !compareIds(e,elem); }); //eslint-disable-line
 | 
					        elements = elements.filter(function (e) {return !compareIds(e, elem); }); // eslint-disable-line
 | 
				
			||||||
        tree.delete(elem);
 | 
					        tree.delete(elem)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    itRootNodeIsBlack(tree);
 | 
					    itRootNodeIsBlack(tree)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("can find every object", function(){
 | 
					    it('can find every object', function () {
 | 
				
			||||||
      for(var id of elements) {
 | 
					      for (var id of elements) {
 | 
				
			||||||
        expect(tree.find(id).id).toEqual(id);
 | 
					        expect(tree.find(id).id).toEqual(id)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("can find every object with lower bound search", function(){
 | 
					    it('can find every object with lower bound search', function () {
 | 
				
			||||||
      for(var id of elements) {
 | 
					      for (var id of elements) {
 | 
				
			||||||
        expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id);
 | 
					        expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    itRedNodesDoNotHaveBlackChildren(tree);
 | 
					    itRedNodesDoNotHaveBlackChildren(tree)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    itBlackHeightOfSubTreesAreEqual(tree);
 | 
					    itBlackHeightOfSubTreesAreEqual(tree)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("iterating over a tree with lower bound yields the right amount of results", function(){
 | 
					    it('iterating over a tree with lower bound yields the right amount of results', function () {
 | 
				
			||||||
      var lowerBound = elements[Math.floor(Math.random() * elements.length)];
 | 
					      var lowerBound = elements[Math.floor(Math.random() * elements.length)]
 | 
				
			||||||
      var expectedResults = elements.filter(function(e, pos){
 | 
					      var expectedResults = elements.filter(function (e, pos) {
 | 
				
			||||||
        return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos;
 | 
					        return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos
 | 
				
			||||||
      }).length;
 | 
					      }).length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var actualResults = 0;
 | 
					      var actualResults = 0
 | 
				
			||||||
      tree.iterate(lowerBound, null, function(val){
 | 
					      tree.iterate(lowerBound, null, function (val) {
 | 
				
			||||||
        expect(val).not.toBeUndefined();
 | 
					        expect(val).not.toBeUndefined()
 | 
				
			||||||
        actualResults++;
 | 
					        actualResults++
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
      expect(expectedResults).toEqual(actualResults);
 | 
					      expect(expectedResults).toEqual(actualResults)
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("iterating over a tree without bounds yield the right amount of results", function(){
 | 
					    it('iterating over a tree without bounds yield the right amount of results', function () {
 | 
				
			||||||
      var lowerBound = null;
 | 
					      var lowerBound = null
 | 
				
			||||||
      var expectedResults = elements.filter(function(e, pos){
 | 
					      var expectedResults = elements.filter(function (e, pos) {
 | 
				
			||||||
        return elements.indexOf(e) === pos;
 | 
					        return elements.indexOf(e) === pos
 | 
				
			||||||
      }).length;
 | 
					      }).length
 | 
				
			||||||
      var actualResults = 0;
 | 
					      var actualResults = 0
 | 
				
			||||||
      tree.iterate(lowerBound, null, function(val){
 | 
					      tree.iterate(lowerBound, null, function (val) {
 | 
				
			||||||
        expect(val).not.toBeUndefined();
 | 
					        expect(val).not.toBeUndefined()
 | 
				
			||||||
        actualResults++;
 | 
					        actualResults++
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
      expect(expectedResults).toEqual(actualResults);
 | 
					      expect(expectedResults).toEqual(actualResults)
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("iterating over a tree with upper bound yields the right amount of results", function(){
 | 
					    it('iterating over a tree with upper bound yields the right amount of results', function () {
 | 
				
			||||||
      var upperBound = elements[Math.floor(Math.random() * elements.length)];
 | 
					      var upperBound = elements[Math.floor(Math.random() * elements.length)]
 | 
				
			||||||
      var expectedResults = elements.filter(function(e, pos){
 | 
					      var expectedResults = elements.filter(function (e, pos) {
 | 
				
			||||||
        return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
 | 
					        return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
 | 
				
			||||||
      }).length;
 | 
					      }).length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var actualResults = 0;
 | 
					      var actualResults = 0
 | 
				
			||||||
      tree.iterate(null, upperBound, function(val){
 | 
					      tree.iterate(null, upperBound, function (val) {
 | 
				
			||||||
        expect(val).not.toBeUndefined();
 | 
					        expect(val).not.toBeUndefined()
 | 
				
			||||||
        actualResults++;
 | 
					        actualResults++
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
      expect(expectedResults).toEqual(actualResults);
 | 
					      expect(expectedResults).toEqual(actualResults)
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("iterating over a tree with upper and lower bounds yield the right amount of results", function(){
 | 
					    it('iterating over a tree with upper and lower bounds yield the right amount of results', function () {
 | 
				
			||||||
      var b1 = elements[Math.floor(Math.random() * elements.length)];
 | 
					      var b1 = elements[Math.floor(Math.random() * elements.length)]
 | 
				
			||||||
      var b2 = elements[Math.floor(Math.random() * elements.length)];
 | 
					      var b2 = elements[Math.floor(Math.random() * elements.length)]
 | 
				
			||||||
      var upperBound, lowerBound;
 | 
					      var upperBound, lowerBound
 | 
				
			||||||
      if (smaller(b1, b2)) {
 | 
					      if (smaller(b1, b2)) {
 | 
				
			||||||
        lowerBound = b1;
 | 
					        lowerBound = b1
 | 
				
			||||||
        upperBound = b2;
 | 
					        upperBound = b2
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        lowerBound = b2;
 | 
					        lowerBound = b2
 | 
				
			||||||
        upperBound = b1;
 | 
					        upperBound = b1
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var expectedResults = elements.filter(function(e, pos){
 | 
					      var expectedResults = elements.filter(function (e, pos) {
 | 
				
			||||||
        return (smaller(lowerBound, e) || compareIds(e, lowerBound))
 | 
					        return (smaller(lowerBound, e) || compareIds(e, lowerBound)) &&
 | 
				
			||||||
                && (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
 | 
					          (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
 | 
				
			||||||
      }).length;
 | 
					      }).length
 | 
				
			||||||
      var actualResults = 0;
 | 
					      var actualResults = 0
 | 
				
			||||||
      tree.iterate(lowerBound, upperBound, function(val){
 | 
					      tree.iterate(lowerBound, upperBound, function (val) {
 | 
				
			||||||
        expect(val).not.toBeUndefined();
 | 
					        expect(val).not.toBeUndefined()
 | 
				
			||||||
        actualResults++;
 | 
					        actualResults++
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
      expect(expectedResults).toEqual(actualResults);
 | 
					      expect(expectedResults).toEqual(actualResults)
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										270
									
								
								src/Struct.js
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								src/Struct.js
									
									
									
									
									
								
							@ -1,36 +1,16 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global copyObject, Y*/
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Op is anything that we could get from the OperationStore.
 | 
					 | 
				
			||||||
type Op = Object;
 | 
					 | 
				
			||||||
type Id = [string, number];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type List = {
 | 
					 | 
				
			||||||
  id: Id,
 | 
					 | 
				
			||||||
  start: Insert,
 | 
					 | 
				
			||||||
  end: Insert
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Insert = {
 | 
					 | 
				
			||||||
  id: Id,
 | 
					 | 
				
			||||||
  left: Insert,
 | 
					 | 
				
			||||||
  right: Insert,
 | 
					 | 
				
			||||||
  origin: Insert,
 | 
					 | 
				
			||||||
  parent: List,
 | 
					 | 
				
			||||||
  content: any
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function compareIds(id1, id2) {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function compareIds (id1, id2) {
 | 
				
			||||||
  if (id1 == null || id2 == null) {
 | 
					  if (id1 == null || id2 == null) {
 | 
				
			||||||
    if (id1 == null && id2 == null) {
 | 
					    if (id1 == null && id2 == null) {
 | 
				
			||||||
      return true;
 | 
					      return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false;
 | 
					    return false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (id1[0] === id2[0] && id1[1] === id2[1]) {
 | 
					  if (id1[0] === id2[0] && id1[1] === id2[1]) {
 | 
				
			||||||
    return true;
 | 
					    return true
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return false;
 | 
					    return false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,26 +22,26 @@ var Struct = {
 | 
				
			|||||||
  */
 | 
					  */
 | 
				
			||||||
  Delete: {
 | 
					  Delete: {
 | 
				
			||||||
    encode: function (op) {
 | 
					    encode: function (op) {
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function (op) {
 | 
					    requiredOps: function (op) {
 | 
				
			||||||
      return [op.target];
 | 
					      return [op.target]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    execute: function* (op) {
 | 
					    execute: function * (op) {
 | 
				
			||||||
      var target = yield* this.getOperation(op.target);
 | 
					      var target = yield* this.getOperation(op.target)
 | 
				
			||||||
      if (!target.deleted) {
 | 
					      if (!target.deleted) {
 | 
				
			||||||
        target.deleted = true;
 | 
					        target.deleted = true
 | 
				
			||||||
        yield* this.setOperation(target);
 | 
					        yield* this.setOperation(target)
 | 
				
			||||||
        var t = this.store.initializedTypes[JSON.stringify(target.parent)];
 | 
					        var t = this.store.initializedTypes[JSON.stringify(target.parent)]
 | 
				
			||||||
        if (t != null) {
 | 
					        if (t != null) {
 | 
				
			||||||
          yield* t._changed(this, copyObject(op));
 | 
					          yield* t._changed(this, copyObject(op))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  Insert: {
 | 
					  Insert: {
 | 
				
			||||||
    /*{
 | 
					    /* {
 | 
				
			||||||
        content: any,
 | 
					        content: any,
 | 
				
			||||||
        left: Id,
 | 
					        left: Id,
 | 
				
			||||||
        right: Id,
 | 
					        right: Id,
 | 
				
			||||||
@ -71,8 +51,9 @@ var Struct = {
 | 
				
			|||||||
        id: this.os.getNextOpId()
 | 
					        id: this.os.getNextOpId()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    encode: function(op){
 | 
					    encode: function (op) {
 | 
				
			||||||
      /*var e = {
 | 
					      /*
 | 
				
			||||||
 | 
					      var e = {
 | 
				
			||||||
        id: op.id,
 | 
					        id: op.id,
 | 
				
			||||||
        left: op.left,
 | 
					        left: op.left,
 | 
				
			||||||
        right: op.right,
 | 
					        right: op.right,
 | 
				
			||||||
@ -80,44 +61,44 @@ var Struct = {
 | 
				
			|||||||
        parent: op.parent,
 | 
					        parent: op.parent,
 | 
				
			||||||
        content: op.content,
 | 
					        content: op.content,
 | 
				
			||||||
        struct: "Insert"
 | 
					        struct: "Insert"
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
      if (op.parentSub != null){
 | 
					      if (op.parentSub != null){
 | 
				
			||||||
        e.parentSub = op.parentSub;
 | 
					        e.parentSub = op.parentSub
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return e;*/
 | 
					      return e;*/
 | 
				
			||||||
      return op;
 | 
					      return op
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function(op){
 | 
					    requiredOps: function (op) {
 | 
				
			||||||
      var ids = [];
 | 
					      var ids = []
 | 
				
			||||||
      if(op.left != null){
 | 
					      if (op.left != null) {
 | 
				
			||||||
        ids.push(op.left);
 | 
					        ids.push(op.left)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if(op.right != null){
 | 
					      if (op.right != null) {
 | 
				
			||||||
        ids.push(op.right);
 | 
					        ids.push(op.right)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      //if(op.right == null && op.left == null) {}
 | 
					      // if(op.right == null && op.left == null) {}
 | 
				
			||||||
      ids.push(op.parent);
 | 
					      ids.push(op.parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (op.opContent != null) {
 | 
					      if (op.opContent != null) {
 | 
				
			||||||
        ids.push(op.opContent);
 | 
					        ids.push(op.opContent)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return ids;
 | 
					      return ids
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    getDistanceToOrigin: function *(op){
 | 
					    getDistanceToOrigin: function *(op) {
 | 
				
			||||||
      if (op.left == null) {
 | 
					      if (op.left == null) {
 | 
				
			||||||
        return 0;
 | 
					        return 0
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        var d = 0;
 | 
					        var d = 0
 | 
				
			||||||
        var o = yield* this.getOperation(op.left);
 | 
					        var o = yield* this.getOperation(op.left)
 | 
				
			||||||
        while (!compareIds(op.origin, (o ? o.id : null))) {
 | 
					        while (!compareIds(op.origin, (o ? o.id : null))) {
 | 
				
			||||||
          d++;
 | 
					          d++
 | 
				
			||||||
          if (o.left == null) {
 | 
					          if (o.left == null) {
 | 
				
			||||||
            break;
 | 
					            break
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            o = yield* this.getOperation(o.left);
 | 
					            o = yield* this.getOperation(o.left)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return d;
 | 
					        return d
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
@ -135,86 +116,86 @@ var Struct = {
 | 
				
			|||||||
    # case 3: $origin > $o.origin
 | 
					    # case 3: $origin > $o.origin
 | 
				
			||||||
    #         $this insert_position is to the left of $o (forever!)
 | 
					    #         $this insert_position is to the left of $o (forever!)
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    execute: function*(op){
 | 
					    execute: function *(op) {
 | 
				
			||||||
      var i; // loop counter
 | 
					      var i // loop counter
 | 
				
			||||||
      var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op); // most cases: 0 (starts from 0)
 | 
					      var distanceToOrigin = i = yield* Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
 | 
				
			||||||
      var o;
 | 
					      var o
 | 
				
			||||||
      var parent;
 | 
					      var parent
 | 
				
			||||||
      var start;
 | 
					      var start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // find o. o is the first conflicting operation
 | 
					      // find o. o is the first conflicting operation
 | 
				
			||||||
      if (op.left != null) {
 | 
					      if (op.left != null) {
 | 
				
			||||||
        o = yield* this.getOperation(op.left);
 | 
					        o = yield* this.getOperation(op.left)
 | 
				
			||||||
        o = (o.right == null) ? null : yield* this.getOperation(o.right);
 | 
					        o = (o.right == null) ? null : yield* this.getOperation(o.right)
 | 
				
			||||||
      } else { // left == null
 | 
					      } else { // left == null
 | 
				
			||||||
        parent = yield* this.getOperation(op.parent);
 | 
					        parent = yield* this.getOperation(op.parent)
 | 
				
			||||||
        let startId = op.parentSub ? parent.map[op.parentSub] : parent.start;
 | 
					        let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
 | 
				
			||||||
        start = startId == null ? null : yield* this.getOperation(startId);
 | 
					        start = startId == null ? null : yield* this.getOperation(startId)
 | 
				
			||||||
        o = start;
 | 
					        o = start
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // handle conflicts
 | 
					      // handle conflicts
 | 
				
			||||||
      while (true) {
 | 
					      while (true) {
 | 
				
			||||||
        if (o != null && !compareIds(o.id, op.right)){
 | 
					        if (o != null && !compareIds(o.id, op.right)) {
 | 
				
			||||||
          var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o);
 | 
					          var oOriginDistance = yield* Struct.Insert.getDistanceToOrigin.call(this, o)
 | 
				
			||||||
          if (oOriginDistance === i) {
 | 
					          if (oOriginDistance === i) {
 | 
				
			||||||
            // case 1
 | 
					            // case 1
 | 
				
			||||||
            if (o.id[0] < op.id[0]) {
 | 
					            if (o.id[0] < op.id[0]) {
 | 
				
			||||||
              op.left = o.id;
 | 
					              op.left = o.id
 | 
				
			||||||
              distanceToOrigin = i + 1;
 | 
					              distanceToOrigin = i + 1
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else if (oOriginDistance < i) {
 | 
					          } else if (oOriginDistance < i) {
 | 
				
			||||||
            // case 2
 | 
					            // case 2
 | 
				
			||||||
            if (i - distanceToOrigin <= oOriginDistance) {
 | 
					            if (i - distanceToOrigin <= oOriginDistance) {
 | 
				
			||||||
              op.left = o.id;
 | 
					              op.left = o.id
 | 
				
			||||||
              distanceToOrigin = i + 1;
 | 
					              distanceToOrigin = i + 1
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            break;
 | 
					            break
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          i++;
 | 
					          i++
 | 
				
			||||||
          o = o.right ? yield* this.getOperation(o.right) : null;
 | 
					          o = o.right ? yield* this.getOperation(o.right) : null
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          break;
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // reconnect..
 | 
					      // reconnect..
 | 
				
			||||||
      var left = null;
 | 
					      var left = null
 | 
				
			||||||
      var right = null;
 | 
					      var right = null
 | 
				
			||||||
      parent = parent || (yield* this.getOperation(op.parent));
 | 
					      parent = parent || (yield* this.getOperation(op.parent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // reconnect left and set right of op
 | 
					      // reconnect left and set right of op
 | 
				
			||||||
      if (op.left != null) {
 | 
					      if (op.left != null) {
 | 
				
			||||||
        left = yield* this.getOperation(op.left);
 | 
					        left = yield* this.getOperation(op.left)
 | 
				
			||||||
        op.right = left.right;
 | 
					        op.right = left.right
 | 
				
			||||||
        left.right = op.id;
 | 
					        left.right = op.id
 | 
				
			||||||
        yield* this.setOperation(left);
 | 
					        yield* this.setOperation(left)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
 | 
					        op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // reconnect right
 | 
					      // reconnect right
 | 
				
			||||||
      if (op.right != null) {
 | 
					      if (op.right != null) {
 | 
				
			||||||
        right = yield* this.getOperation(op.right);
 | 
					        right = yield* this.getOperation(op.right)
 | 
				
			||||||
        right.left = op.id;
 | 
					        right.left = op.id
 | 
				
			||||||
        yield* this.setOperation(right);
 | 
					        yield* this.setOperation(right)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // notify parent
 | 
					      // notify parent
 | 
				
			||||||
      if (op.parentSub != null) {
 | 
					      if (op.parentSub != null) {
 | 
				
			||||||
        if (left == null) {
 | 
					        if (left == null) {
 | 
				
			||||||
          parent.map[op.parentSub] = op.id;
 | 
					          parent.map[op.parentSub] = op.id
 | 
				
			||||||
          yield* this.setOperation(parent);
 | 
					          yield* this.setOperation(parent)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if (right == null || left == null) {
 | 
					        if (right == null || left == null) {
 | 
				
			||||||
          if (right == null) {
 | 
					          if (right == null) {
 | 
				
			||||||
            parent.end = op.id;
 | 
					            parent.end = op.id
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (left == null) {
 | 
					          if (left == null) {
 | 
				
			||||||
            parent.start = op.id;
 | 
					            parent.start = op.id
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          yield* this.setOperation(parent);
 | 
					          yield* this.setOperation(parent)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -229,61 +210,61 @@ var Struct = {
 | 
				
			|||||||
      id: this.os.getNextOpId()
 | 
					      id: this.os.getNextOpId()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    encode: function(op){
 | 
					    encode: function (op) {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        struct: "List",
 | 
					        struct: 'List',
 | 
				
			||||||
        id: op.id,
 | 
					        id: op.id,
 | 
				
			||||||
        type: op.type
 | 
					        type: op.type
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function(){
 | 
					    requiredOps: function () {
 | 
				
			||||||
      /*
 | 
					      /*
 | 
				
			||||||
      var ids = [];
 | 
					      var ids = []
 | 
				
			||||||
      if (op.start != null) {
 | 
					      if (op.start != null) {
 | 
				
			||||||
        ids.push(op.start);
 | 
					        ids.push(op.start)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (op.end != null){
 | 
					      if (op.end != null){
 | 
				
			||||||
        ids.push(op.end);
 | 
					        ids.push(op.end)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return ids;
 | 
					      return ids
 | 
				
			||||||
      */
 | 
					      */
 | 
				
			||||||
      return [];
 | 
					      return []
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    execute: function* (op) {
 | 
					    execute: function * (op) { // eslint-disable-line
 | 
				
			||||||
      op.start = null;
 | 
					      op.start = null
 | 
				
			||||||
      op.end = null;
 | 
					      op.end = null
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    ref: function* (op : Op, pos : number) : Insert {
 | 
					    ref: function * (op, pos) {
 | 
				
			||||||
      if (op.start == null) {
 | 
					      if (op.start == null) {
 | 
				
			||||||
        return null;
 | 
					        return null
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var res = null;
 | 
					      var res = null
 | 
				
			||||||
      var o = yield* this.getOperation(op.start);
 | 
					      var o = yield* this.getOperation(op.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      while ( true ) {
 | 
					      while (true) {
 | 
				
			||||||
        if (!o.deleted) {
 | 
					        if (!o.deleted) {
 | 
				
			||||||
          res = o;
 | 
					          res = o
 | 
				
			||||||
          pos--;
 | 
					          pos--
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (pos >= 0 && o.right != null) {
 | 
					        if (pos >= 0 && o.right != null) {
 | 
				
			||||||
          o = (yield* this.getOperation(o.right));
 | 
					          o = (yield* this.getOperation(o.right))
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          break;
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return res;
 | 
					      return res
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    map: function* (o : Op, f : Function) : Array<any> {
 | 
					    map: function * (o, f) {
 | 
				
			||||||
      o = o.start;
 | 
					      o = o.start
 | 
				
			||||||
      var res = [];
 | 
					      var res = []
 | 
				
			||||||
      while ( o !== null) {
 | 
					      while (o !== null) {
 | 
				
			||||||
        var operation = yield* this.getOperation(o);
 | 
					        var operation = yield* this.getOperation(o)
 | 
				
			||||||
        if (!operation.deleted) {
 | 
					        if (!operation.deleted) {
 | 
				
			||||||
          res.push(f(operation));
 | 
					          res.push(f(operation))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        o = operation.right;
 | 
					        o = operation.right
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return res;
 | 
					      return res
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  Map: {
 | 
					  Map: {
 | 
				
			||||||
@ -295,42 +276,41 @@ var Struct = {
 | 
				
			|||||||
        id: this.os.getNextOpId()
 | 
					        id: this.os.getNextOpId()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    encode: function(op){
 | 
					    encode: function (op) {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        struct: "Map",
 | 
					        struct: 'Map',
 | 
				
			||||||
        type: op.type,
 | 
					        type: op.type,
 | 
				
			||||||
        id: op.id,
 | 
					        id: op.id,
 | 
				
			||||||
        map: {} // overwrite map!!
 | 
					        map: {} // overwrite map!!
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    requiredOps: function(){
 | 
					    requiredOps: function () {
 | 
				
			||||||
      /*
 | 
					      /*
 | 
				
			||||||
      var ids = [];
 | 
					      var ids = []
 | 
				
			||||||
      for (var end in op.map) {
 | 
					      for (var end in op.map) {
 | 
				
			||||||
        ids.push(op.map[end]);
 | 
					        ids.push(op.map[end])
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return ids;
 | 
					      return ids
 | 
				
			||||||
      */
 | 
					      */
 | 
				
			||||||
      return [];
 | 
					      return []
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    execute: function* () {
 | 
					    execute: function * () {},
 | 
				
			||||||
    },
 | 
					    get: function * (op, name) {
 | 
				
			||||||
    get: function* (op, name) {
 | 
					      var oid = op.map[name]
 | 
				
			||||||
      var oid = op.map[name];
 | 
					 | 
				
			||||||
      if (oid != null) {
 | 
					      if (oid != null) {
 | 
				
			||||||
        var res = yield* this.getOperation(oid);
 | 
					        var res = yield* this.getOperation(oid)
 | 
				
			||||||
        return (res == null || res.deleted) ? void 0 : (res.opContent == null
 | 
					        return (res == null || res.deleted) ? void 0 : (res.opContent == null
 | 
				
			||||||
                  ? res.content : yield* this.getType(res.opContent));
 | 
					          ? res.content : yield* this.getType(res.opContent))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    delete: function* (op, name) {
 | 
					    delete: function * (op, name) {
 | 
				
			||||||
      var v = op.map[name] || null;
 | 
					      var v = op.map[name] || null
 | 
				
			||||||
      if (v != null) {
 | 
					      if (v != null) {
 | 
				
			||||||
        yield* Struct.Delete.create.call(this, {
 | 
					        yield* Struct.Delete.create.call(this, {
 | 
				
			||||||
          target: v
 | 
					          target: v
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
Y.Struct = Struct;
 | 
					Y.Struct = Struct
 | 
				
			||||||
 | 
				
			|||||||
@ -1,86 +1,85 @@
 | 
				
			|||||||
 | 
					/* global EventHandler, Y, CustomType, Struct */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;(function () {
 | 
				
			||||||
(function(){
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  class YArray {
 | 
					  class YArray {
 | 
				
			||||||
    constructor (os, _model, idArray, valArray) {
 | 
					    constructor (os, _model, idArray, valArray) {
 | 
				
			||||||
      this.os = os;
 | 
					      this.os = os
 | 
				
			||||||
      this._model = _model;
 | 
					      this._model = _model
 | 
				
			||||||
      // Array of all the operation id's
 | 
					      // Array of all the operation id's
 | 
				
			||||||
      this.idArray = idArray;
 | 
					      this.idArray = idArray
 | 
				
			||||||
      // Array of all the values
 | 
					      // Array of all the values
 | 
				
			||||||
      this.valArray = valArray;
 | 
					      this.valArray = valArray
 | 
				
			||||||
      this.eventHandler = new EventHandler( ops =>{
 | 
					      this.eventHandler = new EventHandler(ops => {
 | 
				
			||||||
        var userEvents = [];
 | 
					        var userEvents = []
 | 
				
			||||||
        for (var i in ops) {
 | 
					        for (var i in ops) {
 | 
				
			||||||
          var op = ops[i];
 | 
					          var op = ops[i]
 | 
				
			||||||
          if (op.struct === "Insert") {
 | 
					          if (op.struct === 'Insert') {
 | 
				
			||||||
            let pos;
 | 
					            let pos
 | 
				
			||||||
            // we check op.left only!,
 | 
					            // we check op.left only!,
 | 
				
			||||||
            // because op.right might not be defined when this is called
 | 
					            // because op.right might not be defined when this is called
 | 
				
			||||||
            if (op.left === null) {
 | 
					            if (op.left === null) {
 | 
				
			||||||
              pos = 0;
 | 
					              pos = 0
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              var sid = JSON.stringify(op.left);
 | 
					              var sid = JSON.stringify(op.left)
 | 
				
			||||||
              pos = this.idArray.indexOf(sid) + 1;
 | 
					              pos = this.idArray.indexOf(sid) + 1
 | 
				
			||||||
              if (pos <= 0) {
 | 
					              if (pos <= 0) {
 | 
				
			||||||
                throw new Error("Unexpected operation!");
 | 
					                throw new Error('Unexpected operation!')
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.idArray.splice(pos, 0, JSON.stringify(op.id));
 | 
					            this.idArray.splice(pos, 0, JSON.stringify(op.id))
 | 
				
			||||||
            this.valArray.splice(pos, 0, op.content);
 | 
					            this.valArray.splice(pos, 0, op.content)
 | 
				
			||||||
            userEvents.push({
 | 
					            userEvents.push({
 | 
				
			||||||
              type: "insert",
 | 
					              type: 'insert',
 | 
				
			||||||
              object: this,
 | 
					              object: this,
 | 
				
			||||||
              index: pos,
 | 
					              index: pos,
 | 
				
			||||||
              length: 1
 | 
					              length: 1
 | 
				
			||||||
            });
 | 
					            })
 | 
				
			||||||
          } else if (op.struct === "Delete") {
 | 
					          } else if (op.struct === 'Delete') {
 | 
				
			||||||
            let pos = this.idArray.indexOf(JSON.stringify(op.target));
 | 
					            let pos = this.idArray.indexOf(JSON.stringify(op.target))
 | 
				
			||||||
            this.idArray.splice(pos, 1);
 | 
					            this.idArray.splice(pos, 1)
 | 
				
			||||||
            this.valArray.splice(pos, 1);
 | 
					            this.valArray.splice(pos, 1)
 | 
				
			||||||
            userEvents.push({
 | 
					            userEvents.push({
 | 
				
			||||||
              type: "delete",
 | 
					              type: 'delete',
 | 
				
			||||||
              object: this,
 | 
					              object: this,
 | 
				
			||||||
              index: pos,
 | 
					              index: pos,
 | 
				
			||||||
              length: 1
 | 
					              length: 1
 | 
				
			||||||
            });
 | 
					            })
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            throw new Error("Unexpected struct!");
 | 
					            throw new Error('Unexpected struct!')
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.eventHandler.callUserEventListeners(userEvents);
 | 
					        this.eventHandler.callUserEventListeners(userEvents)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    get length () {
 | 
					    get length () {
 | 
				
			||||||
      return this.idArray.length;
 | 
					      return this.idArray.length
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    get (pos) {
 | 
					    get (pos) {
 | 
				
			||||||
      if (pos == null || typeof pos !== "number") {
 | 
					      if (pos == null || typeof pos !== 'number') {
 | 
				
			||||||
          throw new Error("pos must be a number!");
 | 
					        throw new Error('pos must be a number!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return this.valArray[pos];
 | 
					      return this.valArray[pos]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    toArray() {
 | 
					    toArray () {
 | 
				
			||||||
      return this.valArray.slice();
 | 
					      return this.valArray.slice()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    insert (pos, contents) {
 | 
					    insert (pos, contents) {
 | 
				
			||||||
      if (typeof pos !== "number") {
 | 
					      if (typeof pos !== 'number') {
 | 
				
			||||||
        throw new Error("pos must be a number!");
 | 
					        throw new Error('pos must be a number!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (!(contents instanceof Array)) {
 | 
					      if (!(contents instanceof Array)) {
 | 
				
			||||||
        throw new Error("contents must be an Array of objects!");
 | 
					        throw new Error('contents must be an Array of objects!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (contents.length === 0) {
 | 
					      if (contents.length === 0) {
 | 
				
			||||||
        return;
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (pos > this.idArray.length || pos < 0) {
 | 
					      if (pos > this.idArray.length || pos < 0) {
 | 
				
			||||||
        throw new Error("This position exceeds the range of the array!");
 | 
					        throw new Error('This position exceeds the range of the array!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1]);
 | 
					      var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var ops = [];
 | 
					      var ops = []
 | 
				
			||||||
      var prevId = mostLeft;
 | 
					      var prevId = mostLeft
 | 
				
			||||||
      for (var i = 0; i < contents.length; i++) {
 | 
					      for (var i = 0; i < contents.length; i++) {
 | 
				
			||||||
        var op = {
 | 
					        var op = {
 | 
				
			||||||
          left: prevId,
 | 
					          left: prevId,
 | 
				
			||||||
@ -90,97 +89,97 @@
 | 
				
			|||||||
          // at the time of creating this operation, and is therefore not defined in idArray
 | 
					          // at the time of creating this operation, and is therefore not defined in idArray
 | 
				
			||||||
          parent: this._model,
 | 
					          parent: this._model,
 | 
				
			||||||
          content: contents[i],
 | 
					          content: contents[i],
 | 
				
			||||||
          struct: "Insert",
 | 
					          struct: 'Insert',
 | 
				
			||||||
          id: this.os.getNextOpId()
 | 
					          id: this.os.getNextOpId()
 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        ops.push(op);
 | 
					 | 
				
			||||||
        prevId = op.id;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      var eventHandler = this.eventHandler;
 | 
					        ops.push(op)
 | 
				
			||||||
      eventHandler.awaitAndPrematurelyCall(ops);
 | 
					        prevId = op.id
 | 
				
			||||||
      this.os.requestTransaction(function*(){
 | 
					      }
 | 
				
			||||||
 | 
					      var eventHandler = this.eventHandler
 | 
				
			||||||
 | 
					      eventHandler.awaitAndPrematurelyCall(ops)
 | 
				
			||||||
 | 
					      this.os.requestTransaction(function *() {
 | 
				
			||||||
        // now we can set the right reference.
 | 
					        // now we can set the right reference.
 | 
				
			||||||
        var mostRight;
 | 
					        var mostRight
 | 
				
			||||||
        if (mostLeft != null) {
 | 
					        if (mostLeft != null) {
 | 
				
			||||||
          mostRight = (yield* this.getOperation(mostLeft)).right;
 | 
					          mostRight = (yield* this.getOperation(mostLeft)).right
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          mostRight = (yield* this.getOperation(ops[0].parent)).start;
 | 
					          mostRight = (yield* this.getOperation(ops[0].parent)).start
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (var j in ops) {
 | 
					        for (var j in ops) {
 | 
				
			||||||
          ops[j].right = mostRight;
 | 
					          ops[j].right = mostRight
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        yield* this.applyCreatedOperations(ops);
 | 
					        yield* this.applyCreatedOperations(ops)
 | 
				
			||||||
        eventHandler.awaitedLastInserts(ops.length);
 | 
					        eventHandler.awaitedLastInserts(ops.length)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    delete (pos, length = 1) {
 | 
					    delete (pos, length = 1) {
 | 
				
			||||||
      if (typeof length !== "number") {
 | 
					      if (typeof length !== 'number') {
 | 
				
			||||||
        throw new Error("pos must be a number!");
 | 
					        throw new Error('pos must be a number!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (typeof pos !== "number") {
 | 
					      if (typeof pos !== 'number') {
 | 
				
			||||||
        throw new Error("pos must be a number!");
 | 
					        throw new Error('pos must be a number!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (pos + length > this.idArray.length || pos < 0 || length < 0) {
 | 
					      if (pos + length > this.idArray.length || pos < 0 || length < 0) {
 | 
				
			||||||
        throw new Error("The deletion range exceeds the range of the array!");
 | 
					        throw new Error('The deletion range exceeds the range of the array!')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (length === 0) {
 | 
					      if (length === 0) {
 | 
				
			||||||
        return;
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      var eventHandler = this.eventHandler;
 | 
					      var eventHandler = this.eventHandler
 | 
				
			||||||
      var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null;
 | 
					      var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null
 | 
				
			||||||
      var dels = [];
 | 
					      var dels = []
 | 
				
			||||||
      for (var i = 0; i < length; i++) {
 | 
					      for (var i = 0; i < length; i++) {
 | 
				
			||||||
        dels.push({
 | 
					        dels.push({
 | 
				
			||||||
          target: JSON.parse(this.idArray[pos + i]),
 | 
					          target: JSON.parse(this.idArray[pos + i]),
 | 
				
			||||||
          struct: "Delete"
 | 
					          struct: 'Delete'
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      eventHandler.awaitAndPrematurelyCall(dels);
 | 
					      eventHandler.awaitAndPrematurelyCall(dels)
 | 
				
			||||||
      this.os.requestTransaction(function*(){
 | 
					      this.os.requestTransaction(function *() {
 | 
				
			||||||
        yield* this.applyCreatedOperations(dels);
 | 
					        yield* this.applyCreatedOperations(dels)
 | 
				
			||||||
        eventHandler.awaitedLastDeletes(dels.length, newLeft);
 | 
					        eventHandler.awaitedLastDeletes(dels.length, newLeft)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    observe (f) {
 | 
					    observe (f) {
 | 
				
			||||||
      this.eventHandler.addUserEventListener(f);
 | 
					      this.eventHandler.addUserEventListener(f)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    *_changed (transaction, op) {
 | 
					    * _changed (transaction, op) {
 | 
				
			||||||
      if (op.struct === "Insert") {
 | 
					      if (op.struct === 'Insert') {
 | 
				
			||||||
        var l = op.left;
 | 
					        var l = op.left
 | 
				
			||||||
        var left;
 | 
					        var left
 | 
				
			||||||
        while (l != null) {
 | 
					        while (l != null) {
 | 
				
			||||||
          left = yield* transaction.getOperation(l);
 | 
					          left = yield* transaction.getOperation(l)
 | 
				
			||||||
          if (!left.deleted) {
 | 
					          if (!left.deleted) {
 | 
				
			||||||
            break;
 | 
					            break
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          l = left.left;
 | 
					          l = left.left
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        op.left = l;
 | 
					        op.left = l
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.eventHandler.receivedOp(op);
 | 
					      this.eventHandler.receivedOp(op)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Y.Array = new CustomType({
 | 
					  Y.Array = new CustomType({
 | 
				
			||||||
    class: YArray,
 | 
					    class: YArray,
 | 
				
			||||||
    createType: function* YArrayCreator () {
 | 
					    createType: function * YArrayCreator () {
 | 
				
			||||||
      var model = {
 | 
					      var model = {
 | 
				
			||||||
        start: null,
 | 
					        start: null,
 | 
				
			||||||
        end: null,
 | 
					        end: null,
 | 
				
			||||||
        struct: "List",
 | 
					        struct: 'List',
 | 
				
			||||||
        type: "Array",
 | 
					        type: 'Array',
 | 
				
			||||||
        id: this.store.getNextOpId()
 | 
					        id: this.store.getNextOpId()
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      yield* this.applyCreatedOperations([model]);
 | 
					 | 
				
			||||||
      return yield* this.createType(model);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    initType: function* YArrayInitializer(os, model){
 | 
					 | 
				
			||||||
      var valArray = [];
 | 
					 | 
				
			||||||
      var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
 | 
					 | 
				
			||||||
        valArray.push(c.content);
 | 
					 | 
				
			||||||
        return JSON.stringify(c.id);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return new YArray(os, model.id, idArray, valArray);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
  });
 | 
					      yield* this.applyCreatedOperations([model])
 | 
				
			||||||
})();
 | 
					      return yield* this.createType(model)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    initType: function * YArrayInitializer (os, model) {
 | 
				
			||||||
 | 
					      var valArray = []
 | 
				
			||||||
 | 
					      var idArray = yield* Struct.List.map.call(this, model, function (c) {
 | 
				
			||||||
 | 
					        valArray.push(c.content)
 | 
				
			||||||
 | 
					        return JSON.stringify(c.id)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      return new YArray(os, model.id, idArray, valArray)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,143 +1,145 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
 | 
				
			||||||
/*eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var numberOfYArrayTests = 80;
 | 
					var numberOfYArrayTests = 80
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Array Type", function(){
 | 
					describe('Array Type', function () {
 | 
				
			||||||
  var y1, y2, y3, flushAll;
 | 
					  var y1, y2, y3, flushAll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
 | 
					  jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
 | 
				
			||||||
  beforeEach(async function(done){
 | 
					  beforeEach(async function (done) {
 | 
				
			||||||
    await createUsers(this, 5);
 | 
					    await createUsers(this, 5)
 | 
				
			||||||
    y1 = this.users[0].root;
 | 
					    y1 = this.users[0].root
 | 
				
			||||||
    y2 = this.users[1].root;
 | 
					    y2 = this.users[1].root
 | 
				
			||||||
    y3 = this.users[2].root;
 | 
					    y3 = this.users[2].root
 | 
				
			||||||
    flushAll = this.users[0].connector.flushAll;
 | 
					    flushAll = this.users[0].connector.flushAll
 | 
				
			||||||
    done();
 | 
					    done()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  afterEach(async function(done) {
 | 
					  afterEach(async function(done) {
 | 
				
			||||||
    await compareAllUsers(this.users);
 | 
					    await compareAllUsers(this.users)
 | 
				
			||||||
    done();
 | 
					    done()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe("Basic tests", function(){
 | 
					  describe('Basic tests', function () {
 | 
				
			||||||
    it("insert three elements, try re-get property", async function(done){
 | 
					    it('insert three elements, try re-get property', async function (done) {
 | 
				
			||||||
      var array = await y1.set("Array", Y.Array);
 | 
					      var array = await y1.set('Array', Y.Array)
 | 
				
			||||||
      array.insert(0, [1, 2, 3]);
 | 
					      array.insert(0, [1, 2, 3])
 | 
				
			||||||
      array = await y1.get("Array"); // re-get property
 | 
					      array = await y1.get('Array') // re-get property
 | 
				
			||||||
      expect(array.toArray()).toEqual([1, 2, 3]);
 | 
					      expect(array.toArray()).toEqual([1, 2, 3])
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic insert in array (handle three conflicts)", async function(done){
 | 
					    it('Basic insert in array (handle three conflicts)', async function (done) {
 | 
				
			||||||
      var l1, l2, l3;
 | 
					      await y1.set('Array', Y.Array)
 | 
				
			||||||
      await y1.set("Array", Y.Array);
 | 
					      await flushAll()
 | 
				
			||||||
      await flushAll();
 | 
					      var l1 = await y1.get('Array')
 | 
				
			||||||
      (l1 = await y1.get("Array")).insert(0, [0]);
 | 
					      l1.insert(0, [0])
 | 
				
			||||||
      (l2 = await y2.get("Array")).insert(0, [1]);
 | 
					      var l2 = await y2.get('Array')
 | 
				
			||||||
      (l3 = await y3.get("Array")).insert(0, [2]);
 | 
					      l2.insert(0, [1])
 | 
				
			||||||
      await flushAll();
 | 
					      var l3 = await y3.get('Array')
 | 
				
			||||||
      expect(l1.toArray()).toEqual(l2.toArray());
 | 
					      l3.insert(0, [2])
 | 
				
			||||||
      expect(l2.toArray()).toEqual(l3.toArray());
 | 
					      await flushAll()
 | 
				
			||||||
      done();
 | 
					      expect(l1.toArray()).toEqual(l2.toArray())
 | 
				
			||||||
    });
 | 
					      expect(l2.toArray()).toEqual(l3.toArray())
 | 
				
			||||||
    it("Basic insert&delete in array (handle three conflicts)", async function(done){
 | 
					      done()
 | 
				
			||||||
      var l1, l2, l3;
 | 
					    })
 | 
				
			||||||
      l1 = await y1.set("Array", Y.Array);
 | 
					    it('Basic insert&delete in array (handle three conflicts)', async function (done) {
 | 
				
			||||||
      l1.insert(0, ["x", "y", "z"]);
 | 
					      var l1, l2, l3
 | 
				
			||||||
      await flushAll();
 | 
					      l1 = await y1.set('Array', Y.Array)
 | 
				
			||||||
      l1.insert(1, [0]);
 | 
					      l1.insert(0, ['x', 'y', 'z'])
 | 
				
			||||||
      l2 = await y2.get("Array");
 | 
					      await flushAll()
 | 
				
			||||||
      l2.delete(0);
 | 
					      l1.insert(1, [0])
 | 
				
			||||||
      l2.delete(1);
 | 
					      l2 = await y2.get('Array')
 | 
				
			||||||
      l3 = await y3.get("Array");
 | 
					      l2.delete(0)
 | 
				
			||||||
      l3.insert(1, [2]);
 | 
					      l2.delete(1)
 | 
				
			||||||
      await flushAll();
 | 
					      l3 = await y3.get('Array')
 | 
				
			||||||
      expect(l1.toArray()).toEqual(l2.toArray());
 | 
					      l3.insert(1, [2])
 | 
				
			||||||
      expect(l2.toArray()).toEqual(l3.toArray());
 | 
					      await flushAll()
 | 
				
			||||||
      expect(l2.toArray()).toEqual([0, 2, "y"]);
 | 
					      expect(l1.toArray()).toEqual(l2.toArray())
 | 
				
			||||||
      done();
 | 
					      expect(l2.toArray()).toEqual(l3.toArray())
 | 
				
			||||||
    });
 | 
					      expect(l2.toArray()).toEqual([0, 2, 'y'])
 | 
				
			||||||
    it("Basic insert. Then delete the whole array", async function(done){
 | 
					      done()
 | 
				
			||||||
      var l1, l2, l3;
 | 
					    })
 | 
				
			||||||
      l1 = await y1.set("Array", Y.Array);
 | 
					    it('Basic insert. Then delete the whole array', async function (done) {
 | 
				
			||||||
      l1.insert(0, ["x", "y", "z"]);
 | 
					      var l1, l2, l3
 | 
				
			||||||
      await flushAll();
 | 
					      l1 = await y1.set('Array', Y.Array)
 | 
				
			||||||
      l1.delete(0, 3);
 | 
					      l1.insert(0, ['x', 'y', 'z'])
 | 
				
			||||||
      l2 = await y2.get("Array");
 | 
					      await flushAll()
 | 
				
			||||||
      l3 = await y3.get("Array");
 | 
					      l1.delete(0, 3)
 | 
				
			||||||
      await flushAll();
 | 
					      l2 = await y2.get('Array')
 | 
				
			||||||
      expect(l1.toArray()).toEqual(l2.toArray());
 | 
					      l3 = await y3.get('Array')
 | 
				
			||||||
      expect(l2.toArray()).toEqual(l3.toArray());
 | 
					      await flushAll()
 | 
				
			||||||
      expect(l2.toArray()).toEqual([]);
 | 
					      expect(l1.toArray()).toEqual(l2.toArray())
 | 
				
			||||||
      done();
 | 
					      expect(l2.toArray()).toEqual(l3.toArray())
 | 
				
			||||||
    });
 | 
					      expect(l2.toArray()).toEqual([])
 | 
				
			||||||
    it("throw insert & delete events", async function(done){
 | 
					      done()
 | 
				
			||||||
      var array = await this.users[0].root.set("array", Y.Array);
 | 
					    })
 | 
				
			||||||
      var event;
 | 
					    it('throw insert & delete events', async function (done) {
 | 
				
			||||||
      array.observe(function(e){
 | 
					      var array = await this.users[0].root.set('array', Y.Array)
 | 
				
			||||||
        event = e;
 | 
					      var event
 | 
				
			||||||
      });
 | 
					      array.observe(function (e) {
 | 
				
			||||||
      array.insert(0, [0]);
 | 
					        event = e
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      array.insert(0, [0])
 | 
				
			||||||
      expect(event).toEqual([{
 | 
					      expect(event).toEqual([{
 | 
				
			||||||
        type: "insert",
 | 
					        type: 'insert',
 | 
				
			||||||
        object: array,
 | 
					        object: array,
 | 
				
			||||||
        index: 0,
 | 
					        index: 0,
 | 
				
			||||||
        length: 1
 | 
					        length: 1
 | 
				
			||||||
      }]);
 | 
					      }])
 | 
				
			||||||
      array.delete(0);
 | 
					      array.delete(0)
 | 
				
			||||||
      expect(event).toEqual([{
 | 
					      expect(event).toEqual([{
 | 
				
			||||||
        type: "delete",
 | 
					        type: 'delete',
 | 
				
			||||||
        object: array,
 | 
					        object: array,
 | 
				
			||||||
        index: 0,
 | 
					        index: 0,
 | 
				
			||||||
        length: 1
 | 
					        length: 1
 | 
				
			||||||
      }]);
 | 
					      }])
 | 
				
			||||||
      await wait(50);
 | 
					      await wait(50)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  describe(`${numberOfYArrayTests} Random tests`, function(){
 | 
					  describe(`${numberOfYArrayTests} Random tests`, function () {
 | 
				
			||||||
    var randomArrayTransactions = [
 | 
					    var randomArrayTransactions = [
 | 
				
			||||||
      function insert (array) {
 | 
					      function insert (array) {
 | 
				
			||||||
        array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]);
 | 
					        array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()])
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      function _delete (array) {
 | 
					      function _delete (array) {
 | 
				
			||||||
        var length = array.toArray().length;
 | 
					        var length = array.toArray().length
 | 
				
			||||||
        if (length > 0) {
 | 
					        if (length > 0) {
 | 
				
			||||||
          array.delete(getRandomNumber(length - 1));
 | 
					          array.delete(getRandomNumber(length - 1))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ];
 | 
					    ]
 | 
				
			||||||
    function compareArrayValues(arrays){
 | 
					    function compareArrayValues (arrays) {
 | 
				
			||||||
      var firstArray;
 | 
					      var firstArray
 | 
				
			||||||
      for (var l of arrays) {
 | 
					      for (var l of arrays) {
 | 
				
			||||||
        var val = l.toArray();
 | 
					        var val = l.toArray()
 | 
				
			||||||
        if (firstArray == null) {
 | 
					        if (firstArray == null) {
 | 
				
			||||||
          firstArray = val;
 | 
					          firstArray = val
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          expect(val).toEqual(firstArray);
 | 
					          expect(val).toEqual(firstArray)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    beforeEach(async function(done){
 | 
					    beforeEach(async function (done) {
 | 
				
			||||||
      await this.users[0].root.set("Array", Y.Array);
 | 
					      await this.users[0].root.set('Array', Y.Array)
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var promises = [];
 | 
					      var promises = []
 | 
				
			||||||
      for (var u = 0; u < this.users.length; u++) {
 | 
					      for (var u = 0; u < this.users.length; u++) {
 | 
				
			||||||
        promises.push(this.users[u].root.get("Array"));
 | 
					        promises.push(this.users[u].root.get('Array'))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.arrays = await Promise.all(promises);
 | 
					      this.arrays = await Promise.all(promises)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("arrays.length equals users.length", async function(done){
 | 
					    it('arrays.length equals users.length', async function (done) { // eslint-disable-line
 | 
				
			||||||
      expect(this.arrays.length).toEqual(this.users.length);
 | 
					      expect(this.arrays.length).toEqual(this.users.length)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it(`succeed after ${numberOfYArrayTests} actions`, async function(done){
 | 
					    it(`succeed after ${numberOfYArrayTests} actions`, async function (done) {
 | 
				
			||||||
      await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests);
 | 
					      await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests)
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      await compareArrayValues(this.arrays);
 | 
					      await compareArrayValues(this.arrays)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										270
									
								
								src/Types/Map.js
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								src/Types/Map.js
									
									
									
									
									
								
							@ -1,76 +1,78 @@
 | 
				
			|||||||
(function(){
 | 
					/* global EventHandler, Y, CustomType, copyObject, compareIds */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;(function () {
 | 
				
			||||||
  class YMap {
 | 
					  class YMap {
 | 
				
			||||||
    constructor (os, model) {
 | 
					    constructor (os, model) {
 | 
				
			||||||
      this._model = model.id;
 | 
					      this._model = model.id
 | 
				
			||||||
      this.os = os;
 | 
					      this.os = os
 | 
				
			||||||
      this.map = copyObject(model.map);
 | 
					      this.map = copyObject(model.map)
 | 
				
			||||||
      this.contents = {};
 | 
					      this.contents = {}
 | 
				
			||||||
      this.opContents = {};
 | 
					      this.opContents = {}
 | 
				
			||||||
      this.eventHandler = new EventHandler( ops =>{
 | 
					      this.eventHandler = new EventHandler(ops => {
 | 
				
			||||||
        var userEvents = [];
 | 
					        var userEvents = []
 | 
				
			||||||
        for (var i in ops) {
 | 
					        for (var i in ops) {
 | 
				
			||||||
          var op = ops[i];
 | 
					          var op = ops[i]
 | 
				
			||||||
          var oldValue;
 | 
					          var oldValue
 | 
				
			||||||
          // key is the name to use to access (op)content
 | 
					          // key is the name to use to access (op)content
 | 
				
			||||||
          var key = op.struct === "Delete" ? op.key : op.parentSub;
 | 
					          var key = op.struct === 'Delete' ? op.key : op.parentSub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // compute oldValue
 | 
					          // compute oldValue
 | 
				
			||||||
          if (this.opContents[key] != null) {
 | 
					          if (this.opContents[key] != null) {
 | 
				
			||||||
            let prevType = this.opContents[key];
 | 
					            let prevType = this.opContents[key]
 | 
				
			||||||
            oldValue = () => { //eslint-disable-line
 | 
					            oldValue = () => {// eslint-disable-line
 | 
				
			||||||
              let def = Promise.defer();
 | 
					              let def = Promise.defer()
 | 
				
			||||||
              this.os.requestTransaction(function*(){//eslint-disable-line
 | 
					              this.os.requestTransaction(function *() {// eslint-disable-line
 | 
				
			||||||
                def.resolve(yield* this.getType(prevType));
 | 
					                def.resolve(yield* this.getType(prevType))
 | 
				
			||||||
              });
 | 
					              })
 | 
				
			||||||
              return def.promise;
 | 
					              return def.promise
 | 
				
			||||||
            };
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            oldValue = this.contents[key];
 | 
					            oldValue = this.contents[key]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          // compute op event
 | 
					          // compute op event
 | 
				
			||||||
          if (op.struct === "Insert"){
 | 
					          if (op.struct === 'Insert') {
 | 
				
			||||||
            if (op.left === null) {
 | 
					            if (op.left === null) {
 | 
				
			||||||
              if (op.opContent != null) {
 | 
					              if (op.opContent != null) {
 | 
				
			||||||
                delete this.contents[key];
 | 
					                delete this.contents[key]
 | 
				
			||||||
                this.opContents[key] = op.opContent;
 | 
					                this.opContents[key] = op.opContent
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                delete this.opContents[key];
 | 
					                delete this.opContents[key]
 | 
				
			||||||
                this.contents[key] = op.content;
 | 
					                this.contents[key] = op.content
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              this.map[key] = op.id;
 | 
					              this.map[key] = op.id
 | 
				
			||||||
              var insertEvent = {
 | 
					              var insertEvent = {
 | 
				
			||||||
                name: key,
 | 
					                name: key,
 | 
				
			||||||
                object: this
 | 
					                object: this
 | 
				
			||||||
              };
 | 
					              }
 | 
				
			||||||
              if (oldValue === undefined) {
 | 
					              if (oldValue === undefined) {
 | 
				
			||||||
                insertEvent.type = "add";
 | 
					                insertEvent.type = 'add'
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                insertEvent.type = "update";
 | 
					                insertEvent.type = 'update'
 | 
				
			||||||
                insertEvent.oldValue = oldValue;
 | 
					                insertEvent.oldValue = oldValue
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              userEvents.push(insertEvent);
 | 
					              userEvents.push(insertEvent)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else if (op.struct === "Delete") {
 | 
					          } else if (op.struct === 'Delete') {
 | 
				
			||||||
            if (compareIds(this.map[key], op.target)) {
 | 
					            if (compareIds(this.map[key], op.target)) {
 | 
				
			||||||
              if (this.opContents[key] != null) {
 | 
					              if (this.opContents[key] != null) {
 | 
				
			||||||
                delete this.opContents[key];
 | 
					                delete this.opContents[key]
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                delete this.contents[key];
 | 
					                delete this.contents[key]
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              var deleteEvent = {
 | 
					              var deleteEvent = {
 | 
				
			||||||
                name: key,
 | 
					                name: key,
 | 
				
			||||||
                object: this,
 | 
					                object: this,
 | 
				
			||||||
                oldValue: oldValue,
 | 
					                oldValue: oldValue,
 | 
				
			||||||
                type: "delete"
 | 
					                type: 'delete'
 | 
				
			||||||
              };
 | 
					              }
 | 
				
			||||||
              userEvents.push(deleteEvent);
 | 
					              userEvents.push(deleteEvent)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            throw new Error("Unexpected Operation!");
 | 
					            throw new Error('Unexpected Operation!')
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.eventHandler.callUserEventListeners(userEvents);
 | 
					        this.eventHandler.callUserEventListeners(userEvents)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    get (key) {
 | 
					    get (key) {
 | 
				
			||||||
      // return property.
 | 
					      // return property.
 | 
				
			||||||
@ -78,34 +80,34 @@
 | 
				
			|||||||
      // if property is a type, return a promise
 | 
					      // if property is a type, return a promise
 | 
				
			||||||
      if (this.opContents[key] == null) {
 | 
					      if (this.opContents[key] == null) {
 | 
				
			||||||
        if (key == null) {
 | 
					        if (key == null) {
 | 
				
			||||||
          return copyObject(this.contents);
 | 
					          return copyObject(this.contents)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return this.contents[key];
 | 
					          return this.contents[key]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        let def = Promise.defer();
 | 
					        let def = Promise.defer()
 | 
				
			||||||
        var oid = this.opContents[key];
 | 
					        var oid = this.opContents[key]
 | 
				
			||||||
        this.os.requestTransaction(function*(){
 | 
					        this.os.requestTransaction(function *() {
 | 
				
			||||||
          def.resolve(yield* this.getType(oid));
 | 
					          def.resolve(yield* this.getType(oid))
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
        return def.promise;
 | 
					        return def.promise
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    delete (key) {
 | 
					    delete (key) {
 | 
				
			||||||
      var right = this.map[key];
 | 
					      var right = this.map[key]
 | 
				
			||||||
      if (right != null) {
 | 
					      if (right != null) {
 | 
				
			||||||
        var del = {
 | 
					        var del = {
 | 
				
			||||||
          target: right,
 | 
					          target: right,
 | 
				
			||||||
          struct: "Delete"
 | 
					          struct: 'Delete'
 | 
				
			||||||
        };
 | 
					        }
 | 
				
			||||||
        var eventHandler = this.eventHandler;
 | 
					        var eventHandler = this.eventHandler
 | 
				
			||||||
        var modDel = copyObject(del);
 | 
					        var modDel = copyObject(del)
 | 
				
			||||||
        modDel.key = key;
 | 
					        modDel.key = key
 | 
				
			||||||
        eventHandler.awaitAndPrematurelyCall([modDel]);
 | 
					        eventHandler.awaitAndPrematurelyCall([modDel])
 | 
				
			||||||
        this.os.requestTransaction(function*(){
 | 
					        this.os.requestTransaction(function *() {
 | 
				
			||||||
          yield* this.applyCreatedOperations([del]);
 | 
					          yield* this.applyCreatedOperations([del])
 | 
				
			||||||
          eventHandler.awaitedLastDeletes(1);
 | 
					          eventHandler.awaitedLastDeletes(1)
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    set (key, value) {
 | 
					    set (key, value) {
 | 
				
			||||||
@ -113,108 +115,108 @@
 | 
				
			|||||||
      // if property is a type, return a promise
 | 
					      // if property is a type, return a promise
 | 
				
			||||||
      // if not, apply immediately on this type an call event
 | 
					      // if not, apply immediately on this type an call event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var right = this.map[key] || null;
 | 
					      var right = this.map[key] || null
 | 
				
			||||||
      var insert = {
 | 
					      var insert = {
 | 
				
			||||||
        left: null,
 | 
					        left: null,
 | 
				
			||||||
        right: right,
 | 
					        right: right,
 | 
				
			||||||
        origin: null,
 | 
					        origin: null,
 | 
				
			||||||
        parent: this._model,
 | 
					        parent: this._model,
 | 
				
			||||||
        parentSub: key,
 | 
					        parentSub: key,
 | 
				
			||||||
        struct: "Insert"
 | 
					        struct: 'Insert'
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      var def = Promise.defer();
 | 
					 | 
				
			||||||
      if ( value instanceof CustomType) {
 | 
					 | 
				
			||||||
        // construct a new type
 | 
					 | 
				
			||||||
        this.os.requestTransaction(function*(){
 | 
					 | 
				
			||||||
          var type = yield* value.createType.call(this);
 | 
					 | 
				
			||||||
          insert.opContent = type._model;
 | 
					 | 
				
			||||||
          insert.id = this.store.getNextOpId();
 | 
					 | 
				
			||||||
          yield* this.applyCreatedOperations([insert]);
 | 
					 | 
				
			||||||
          def.resolve(type);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        insert.content = value;
 | 
					 | 
				
			||||||
        insert.id = this.os.getNextOpId();
 | 
					 | 
				
			||||||
        var eventHandler = this.eventHandler;
 | 
					 | 
				
			||||||
        eventHandler.awaitAndPrematurelyCall([insert]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.os.requestTransaction(function*(){
 | 
					 | 
				
			||||||
          yield* this.applyCreatedOperations([insert]);
 | 
					 | 
				
			||||||
          eventHandler.awaitedLastInserts(1);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        def.resolve(value);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return def.promise;
 | 
					      var def = Promise.defer()
 | 
				
			||||||
 | 
					      if (value instanceof CustomType) {
 | 
				
			||||||
 | 
					        // construct a new type
 | 
				
			||||||
 | 
					        this.os.requestTransaction(function *() {
 | 
				
			||||||
 | 
					          var type = yield* value.createType.call(this)
 | 
				
			||||||
 | 
					          insert.opContent = type._model
 | 
				
			||||||
 | 
					          insert.id = this.store.getNextOpId()
 | 
				
			||||||
 | 
					          yield* this.applyCreatedOperations([insert])
 | 
				
			||||||
 | 
					          def.resolve(type)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        insert.content = value
 | 
				
			||||||
 | 
					        insert.id = this.os.getNextOpId()
 | 
				
			||||||
 | 
					        var eventHandler = this.eventHandler
 | 
				
			||||||
 | 
					        eventHandler.awaitAndPrematurelyCall([insert])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.os.requestTransaction(function *() {
 | 
				
			||||||
 | 
					          yield* this.applyCreatedOperations([insert])
 | 
				
			||||||
 | 
					          eventHandler.awaitedLastInserts(1)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        def.resolve(value)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return def.promise
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    observe (f) {
 | 
					    observe (f) {
 | 
				
			||||||
      this.eventHandler.addUserEventListener(f);
 | 
					      this.eventHandler.addUserEventListener(f)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    unobserve (f) {
 | 
					    unobserve (f) {
 | 
				
			||||||
      this.eventHandler.removeUserEventListener(f);
 | 
					      this.eventHandler.removeUserEventListener(f)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    observePath (path, f) {
 | 
					    observePath (path, f) {
 | 
				
			||||||
      var self = this;
 | 
					      var self = this
 | 
				
			||||||
      if (path.length === 0) {
 | 
					      if (path.length === 0) {
 | 
				
			||||||
        this.observe(f);
 | 
					        this.observe(f)
 | 
				
			||||||
        return Promise.resolve(function(){
 | 
					        return Promise.resolve(function () {
 | 
				
			||||||
          self.unobserve(f);
 | 
					          self.unobserve(f)
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        var deleteChildObservers;
 | 
					        var deleteChildObservers
 | 
				
			||||||
        var resetObserverPath = function(){
 | 
					        var resetObserverPath = function () {
 | 
				
			||||||
          var promise = self.get(path[0]);
 | 
					          var promise = self.get(path[0])
 | 
				
			||||||
          if (!promise instanceof Promise) {
 | 
					          if (!promise instanceof Promise) {
 | 
				
			||||||
            // its either not defined or a premitive value
 | 
					            // its either not defined or a premitive value
 | 
				
			||||||
            promise = self.set(path[0], Y.Map);
 | 
					            promise = self.set(path[0], Y.Map)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return promise.then(function(map){
 | 
					          return promise.then(function (map) {
 | 
				
			||||||
            return map.observePath(path.slice(1), f);
 | 
					            return map.observePath(path.slice(1), f)
 | 
				
			||||||
          }).then(function(_deleteChildObservers){
 | 
					          }).then(function (_deleteChildObservers) {
 | 
				
			||||||
            deleteChildObservers = _deleteChildObservers;
 | 
					            deleteChildObservers = _deleteChildObservers
 | 
				
			||||||
            return Promise.resolve();
 | 
					            return Promise.resolve()
 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        var observer = function(events){
 | 
					 | 
				
			||||||
          for (var e in events) {
 | 
					 | 
				
			||||||
            var event = events[e];
 | 
					 | 
				
			||||||
            if (event.name === path[0]) {
 | 
					 | 
				
			||||||
              deleteChildObservers();
 | 
					 | 
				
			||||||
              if (event.type === "add" || event.type === "update") {
 | 
					 | 
				
			||||||
                resetObserverPath();
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        self.observe(observer);
 | 
					 | 
				
			||||||
        return resetObserverPath().then(
 | 
					 | 
				
			||||||
          Promise.resolve(function(){
 | 
					 | 
				
			||||||
            deleteChildObservers();
 | 
					 | 
				
			||||||
            self.unobserve(observer);
 | 
					 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
        );
 | 
					        }
 | 
				
			||||||
 | 
					        var observer = function (events) {
 | 
				
			||||||
 | 
					          for (var e in events) {
 | 
				
			||||||
 | 
					            var event = events[e]
 | 
				
			||||||
 | 
					            if (event.name === path[0]) {
 | 
				
			||||||
 | 
					              deleteChildObservers()
 | 
				
			||||||
 | 
					              if (event.type === 'add' || event.type === 'update') {
 | 
				
			||||||
 | 
					                resetObserverPath()
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
    *_changed (transaction, op) {
 | 
					 | 
				
			||||||
      if (op.struct === "Delete") {
 | 
					 | 
				
			||||||
        op.key = (yield* transaction.getOperation(op.target)).parentSub;
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
      this.eventHandler.receivedOp(op);
 | 
					        }
 | 
				
			||||||
 | 
					        self.observe(observer)
 | 
				
			||||||
 | 
					        return resetObserverPath().then(
 | 
				
			||||||
 | 
					          Promise.resolve(function () {
 | 
				
			||||||
 | 
					            deleteChildObservers()
 | 
				
			||||||
 | 
					            self.unobserve(observer)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    * _changed (transaction, op) {
 | 
				
			||||||
 | 
					      if (op.struct === 'Delete') {
 | 
				
			||||||
 | 
					        op.key = (yield* transaction.getOperation(op.target)).parentSub
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.eventHandler.receivedOp(op)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  Y.Map = new CustomType({
 | 
					  Y.Map = new CustomType({
 | 
				
			||||||
    class: YMap,
 | 
					    class: YMap,
 | 
				
			||||||
    createType: function* YMapCreator(){
 | 
					    createType: function * YMapCreator () {
 | 
				
			||||||
      var model = {
 | 
					      var model = {
 | 
				
			||||||
        map: {},
 | 
					        map: {},
 | 
				
			||||||
        struct: "Map",
 | 
					        struct: 'Map',
 | 
				
			||||||
        type: "Map",
 | 
					        type: 'Map',
 | 
				
			||||||
        id: this.store.getNextOpId()
 | 
					        id: this.store.getNextOpId()
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      yield* this.applyCreatedOperations([model]);
 | 
					 | 
				
			||||||
      return yield* this.createType(model);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    initType: function* YMapInitializer(os, model){
 | 
					 | 
				
			||||||
      return new YMap(os, model);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
  });
 | 
					      yield* this.applyCreatedOperations([model])
 | 
				
			||||||
})();
 | 
					      return yield* this.createType(model)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    initType: function * YMapInitializer (os, model) { // eslint-disable-line
 | 
				
			||||||
 | 
					      return new YMap(os, model)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,207 +1,207 @@
 | 
				
			|||||||
/* @flow */
 | 
					/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
 | 
				
			||||||
/*eslint-env browser,jasmine */
 | 
					/* eslint-env browser,jasmine */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var numberOfYMapTests = 100;
 | 
					var numberOfYMapTests = 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Map Type", function(){
 | 
					describe('Map Type', function () {
 | 
				
			||||||
  var y1, y2, y3, y4, flushAll;
 | 
					  var y1, y2, y3, y4, flushAll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
 | 
					  jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
 | 
				
			||||||
  beforeEach(async function(done){
 | 
					  beforeEach(async function (done) {
 | 
				
			||||||
    await createUsers(this, 5);
 | 
					    await createUsers(this, 5)
 | 
				
			||||||
    y1 = this.users[0].root;
 | 
					    y1 = this.users[0].root
 | 
				
			||||||
    y2 = this.users[1].root;
 | 
					    y2 = this.users[1].root
 | 
				
			||||||
    y3 = this.users[2].root;
 | 
					    y3 = this.users[2].root
 | 
				
			||||||
    y4 = this.users[3].root;
 | 
					    y4 = this.users[3].root
 | 
				
			||||||
    flushAll = this.users[0].connector.flushAll;
 | 
					    flushAll = this.users[0].connector.flushAll
 | 
				
			||||||
    done();
 | 
					    done()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  afterEach(async function(done) {
 | 
					  afterEach(async function(done) {
 | 
				
			||||||
    await compareAllUsers(this.users);
 | 
					    await compareAllUsers(this.users)
 | 
				
			||||||
    done();
 | 
					    done()
 | 
				
			||||||
  }, 5000);
 | 
					  }, 5000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe("Basic tests", function(){
 | 
					  describe('Basic tests', function () {
 | 
				
			||||||
    it("Basic get&set of Map property (converge via sync)", async function(done){
 | 
					    it('Basic get&set of Map property (converge via sync)', async function (done) {
 | 
				
			||||||
      y1.set("stuff", "stuffy");
 | 
					      y1.set('stuff', 'stuffy')
 | 
				
			||||||
      expect(y1.get("stuff")).toEqual("stuffy");
 | 
					      expect(y1.get('stuff')).toEqual('stuffy')
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var u = this.users[key].root;
 | 
					        var u = this.users[key].root
 | 
				
			||||||
        expect(u.get("stuff")).toEqual("stuffy");
 | 
					        expect(u.get('stuff')).toEqual('stuffy')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Map can set custom types (Map)", async function(done){
 | 
					    it('Map can set custom types (Map)', async function (done) {
 | 
				
			||||||
      var map = await y1.set("Map", Y.Map);
 | 
					      var map = await y1.set('Map', Y.Map)
 | 
				
			||||||
      map.set("one", 1);
 | 
					      map.set('one', 1)
 | 
				
			||||||
      map = await y1.get("Map");
 | 
					      map = await y1.get('Map')
 | 
				
			||||||
      expect(map.get("one")).toEqual(1);
 | 
					      expect(map.get('one')).toEqual(1)
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Map can set custom types (Array)", async function(done){
 | 
					    it('Map can set custom types (Array)', async function (done) {
 | 
				
			||||||
      var array = await y1.set("Array", Y.Array);
 | 
					      var array = await y1.set('Array', Y.Array)
 | 
				
			||||||
      array.insert(0, [1, 2, 3]);
 | 
					      array.insert(0, [1, 2, 3])
 | 
				
			||||||
      array = await y1.get("Array");
 | 
					      array = await y1.get('Array')
 | 
				
			||||||
      expect(array.toArray()).toEqual([1, 2, 3]);
 | 
					      expect(array.toArray()).toEqual([1, 2, 3])
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic get&set of Map property (converge via update)", async function(done){
 | 
					    it('Basic get&set of Map property (converge via update)', async function (done) {
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "stuffy");
 | 
					      y1.set('stuff', 'stuffy')
 | 
				
			||||||
      expect(y1.get("stuff")).toEqual("stuffy");
 | 
					      expect(y1.get('stuff')).toEqual('stuffy')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var r = this.users[key].root;
 | 
					        var r = this.users[key].root
 | 
				
			||||||
        expect(r.get("stuff")).toEqual("stuffy");
 | 
					        expect(r.get('stuff')).toEqual('stuffy')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic get&set of Map property (handle conflict)", async function(done){
 | 
					    it('Basic get&set of Map property (handle conflict)', async function (done) {
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "c0");
 | 
					      y1.set('stuff', 'c0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      y2.set("stuff", "c1");
 | 
					      y2.set('stuff', 'c1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var u = this.users[key];
 | 
					        var u = this.users[key]
 | 
				
			||||||
        expect(u.root.get("stuff")).toEqual("c0");
 | 
					        expect(u.root.get('stuff')).toEqual('c0')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic get&set&delete of Map property (handle conflict)", async function(done){
 | 
					    it('Basic get&set&delete of Map property (handle conflict)', async function (done) {
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "c0");
 | 
					      y1.set('stuff', 'c0')
 | 
				
			||||||
      y1.delete("stuff");
 | 
					      y1.delete('stuff')
 | 
				
			||||||
      y2.set("stuff", "c1");
 | 
					      y2.set('stuff', 'c1')
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var u = this.users[key];
 | 
					        var u = this.users[key]
 | 
				
			||||||
        expect(u.root.get("stuff")).toBeUndefined();
 | 
					        expect(u.root.get('stuff')).toBeUndefined()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic get&set of Map property (handle three conflicts)", async function(done){
 | 
					    it('Basic get&set of Map property (handle three conflicts)', async function (done) {
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "c0");
 | 
					      y1.set('stuff', 'c0')
 | 
				
			||||||
      y2.set("stuff", "c1");
 | 
					      y2.set('stuff', 'c1')
 | 
				
			||||||
      y2.set("stuff", "c2");
 | 
					      y2.set('stuff', 'c2')
 | 
				
			||||||
      y3.set("stuff", "c3");
 | 
					      y3.set('stuff', 'c3')
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var u = this.users[key];
 | 
					        var u = this.users[key]
 | 
				
			||||||
        expect(u.root.get("stuff")).toEqual("c0");
 | 
					        expect(u.root.get('stuff')).toEqual('c0')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("Basic get&set&delete of Map property (handle three conflicts)", async function(done){
 | 
					    it('Basic get&set&delete of Map property (handle three conflicts)', async function (done) {
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "c0");
 | 
					      y1.set('stuff', 'c0')
 | 
				
			||||||
      y2.set("stuff", "c1");
 | 
					      y2.set('stuff', 'c1')
 | 
				
			||||||
      y2.set("stuff", "c2");
 | 
					      y2.set('stuff', 'c2')
 | 
				
			||||||
      y3.set("stuff", "c3");
 | 
					      y3.set('stuff', 'c3')
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.set("stuff", "deleteme");
 | 
					      y1.set('stuff', 'deleteme')
 | 
				
			||||||
      y1.delete("stuff");
 | 
					      y1.delete('stuff')
 | 
				
			||||||
      y2.set("stuff", "c1");
 | 
					      y2.set('stuff', 'c1')
 | 
				
			||||||
      y3.set("stuff", "c2");
 | 
					      y3.set('stuff', 'c2')
 | 
				
			||||||
      y4.set("stuff", "c3");
 | 
					      y4.set('stuff', 'c3')
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (var key in this.users) {
 | 
					      for (var key in this.users) {
 | 
				
			||||||
        var u = this.users[key];
 | 
					        var u = this.users[key]
 | 
				
			||||||
        expect(u.root.get("stuff")).toBeUndefined();
 | 
					        expect(u.root.get('stuff')).toBeUndefined()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await compareAllUsers(this.users);
 | 
					      await compareAllUsers(this.users)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it("throws add & update & delete events (with type and primitive content)", async function(done){
 | 
					    it('throws add & update & delete events (with type and primitive content)', async function (done) {
 | 
				
			||||||
      var event;
 | 
					      var event
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      y1.observe(function(e){
 | 
					      y1.observe(function (e) {
 | 
				
			||||||
        event = e; // just put it on event, should be thrown synchronously anyway
 | 
					        event = e // just put it on event, should be thrown synchronously anyway
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
      y1.set("stuff", 4);
 | 
					      y1.set('stuff', 4)
 | 
				
			||||||
      expect(event).toEqual([{
 | 
					      expect(event).toEqual([{
 | 
				
			||||||
        type: "add",
 | 
					        type: 'add',
 | 
				
			||||||
        object: y1,
 | 
					        object: y1,
 | 
				
			||||||
        name: "stuff"
 | 
					        name: 'stuff'
 | 
				
			||||||
      }]);
 | 
					      }])
 | 
				
			||||||
      // update, oldValue is in contents
 | 
					      // update, oldValue is in contents
 | 
				
			||||||
      await y1.set("stuff", Y.Array);
 | 
					      await y1.set('stuff', Y.Array)
 | 
				
			||||||
      expect(event).toEqual([{
 | 
					      expect(event).toEqual([{
 | 
				
			||||||
        type: "update",
 | 
					        type: 'update',
 | 
				
			||||||
        object: y1,
 | 
					        object: y1,
 | 
				
			||||||
        name: "stuff",
 | 
					        name: 'stuff',
 | 
				
			||||||
        oldValue: 4
 | 
					        oldValue: 4
 | 
				
			||||||
      }]);
 | 
					      }])
 | 
				
			||||||
      y1.get("stuff").then(function(replacedArray){
 | 
					      y1.get('stuff').then(function (replacedArray) {
 | 
				
			||||||
        // update, oldValue is in opContents
 | 
					        // update, oldValue is in opContents
 | 
				
			||||||
        y1.set("stuff", 5);
 | 
					        y1.set('stuff', 5)
 | 
				
			||||||
        var getYArray = event[0].oldValue;
 | 
					        var getYArray = event[0].oldValue
 | 
				
			||||||
        expect(typeof getYArray.constructor === "function").toBeTruthy();
 | 
					        expect(typeof getYArray.constructor === 'function').toBeTruthy()
 | 
				
			||||||
        getYArray().then(function(array){
 | 
					        getYArray().then(function (array) {
 | 
				
			||||||
          expect(array).toEqual(replacedArray);
 | 
					          expect(array).toEqual(replacedArray)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // delete
 | 
					          // delete
 | 
				
			||||||
          y1.delete("stuff");
 | 
					          y1.delete('stuff')
 | 
				
			||||||
          expect(event).toEqual([{
 | 
					          expect(event).toEqual([{
 | 
				
			||||||
            type: "delete",
 | 
					            type: 'delete',
 | 
				
			||||||
            name: "stuff",
 | 
					            name: 'stuff',
 | 
				
			||||||
            object: y1,
 | 
					            object: y1,
 | 
				
			||||||
            oldValue: 5
 | 
					            oldValue: 5
 | 
				
			||||||
          }]);
 | 
					          }])
 | 
				
			||||||
          done();
 | 
					          done()
 | 
				
			||||||
        });
 | 
					        })
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
  describe(`${numberOfYMapTests} Random tests`, function(){
 | 
					  describe(`${numberOfYMapTests} Random tests`, function () {
 | 
				
			||||||
    var randomMapTransactions = [
 | 
					    var randomMapTransactions = [
 | 
				
			||||||
      function set (map) {
 | 
					      function set (map) {
 | 
				
			||||||
        map.set("somekey", getRandomNumber());
 | 
					        map.set('somekey', getRandomNumber())
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      function delete_ (map) {
 | 
					      function delete_ (map) {
 | 
				
			||||||
        map.delete("somekey");
 | 
					        map.delete('somekey')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ];
 | 
					    ]
 | 
				
			||||||
    function compareMapValues(maps){
 | 
					    function compareMapValues (maps) {
 | 
				
			||||||
      var firstMap;
 | 
					      var firstMap
 | 
				
			||||||
      for (var map of maps) {
 | 
					      for (var map of maps) {
 | 
				
			||||||
        var val = map.get();
 | 
					        var val = map.get()
 | 
				
			||||||
        if (firstMap == null) {
 | 
					        if (firstMap == null) {
 | 
				
			||||||
          firstMap = val;
 | 
					          firstMap = val
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          expect(val).toEqual(firstMap);
 | 
					          expect(val).toEqual(firstMap)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    beforeEach(async function(done){
 | 
					    beforeEach(async function (done) {
 | 
				
			||||||
      await y1.set("Map", Y.Map);
 | 
					      await y1.set('Map', Y.Map)
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var promises = [];
 | 
					      var promises = []
 | 
				
			||||||
      for (var u = 0; u < this.users.length; u++) {
 | 
					      for (var u = 0; u < this.users.length; u++) {
 | 
				
			||||||
        promises.push(this.users[u].root.get("Map"));
 | 
					        promises.push(this.users[u].root.get('Map'))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.maps = await Promise.all(promises);
 | 
					      this.maps = await Promise.all(promises)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    it(`succeed after ${numberOfYMapTests} actions`, async function(done){
 | 
					    it(`succeed after ${numberOfYMapTests} actions`, async function (done) {
 | 
				
			||||||
      await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests);
 | 
					      await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests)
 | 
				
			||||||
      await flushAll();
 | 
					      await flushAll()
 | 
				
			||||||
      await compareMapValues(this.maps);
 | 
					      await compareMapValues(this.maps)
 | 
				
			||||||
      done();
 | 
					      done()
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
});
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -1,193 +1,194 @@
 | 
				
			|||||||
 | 
					/* global Y, CustomType */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function(){
 | 
					;(function () {
 | 
				
			||||||
  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)
 | 
				
			||||||
      this.textfields = [];
 | 
					      this.textfields = []
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    toString () {
 | 
					    toString () {
 | 
				
			||||||
      return this.valArray.join("");
 | 
					      return this.valArray.join('')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    insert (pos, content) {
 | 
					    insert (pos, content) {
 | 
				
			||||||
      super(pos, content.split(""));
 | 
					      super(pos, content.split(''))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    bind (textfield, domRoot) {
 | 
					    bind (textfield, domRoot) {
 | 
				
			||||||
        domRoot = domRoot || window; //eslint-disable-line
 | 
					      domRoot = domRoot || window; // eslint-disable-line
 | 
				
			||||||
      if (domRoot.getSelection == null) {
 | 
					      if (domRoot.getSelection == null) {
 | 
				
			||||||
          domRoot = window;//eslint-disable-line
 | 
					        domRoot = window;// eslint-disable-line
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // don't duplicate!
 | 
					      // don't duplicate!
 | 
				
			||||||
      for (var t in this.textfields) {
 | 
					      for (var t in this.textfields) {
 | 
				
			||||||
        if (this.textfields[t] === textfield) {
 | 
					        if (this.textfields[t] === textfield) {
 | 
				
			||||||
            return;
 | 
					          return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
        var creatorToken = false;
 | 
					      var creatorToken = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var word = this;
 | 
					      var word = this
 | 
				
			||||||
        textfield.value = this.toString();
 | 
					      textfield.value = this.toString()
 | 
				
			||||||
        this.textfields.push(textfield);
 | 
					      this.textfields.push(textfield)
 | 
				
			||||||
        var createRange, writeRange, writeContent;
 | 
					      var createRange, writeRange, writeContent
 | 
				
			||||||
        if(textfield.selectionStart != null && textfield.setSelectionRange != null) {
 | 
					      if (textfield.selectionStart != null && textfield.setSelectionRange != null) {
 | 
				
			||||||
        createRange = function (fix) {
 | 
					        createRange = function (fix) {
 | 
				
			||||||
            var left = textfield.selectionStart;
 | 
					          var left = textfield.selectionStart
 | 
				
			||||||
            var right = textfield.selectionEnd;
 | 
					          var right = textfield.selectionEnd
 | 
				
			||||||
          if (fix != null) {
 | 
					          if (fix != null) {
 | 
				
			||||||
              left = fix(left);
 | 
					            left = fix(left)
 | 
				
			||||||
              right = fix(right);
 | 
					            right = fix(right)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return {
 | 
					          return {
 | 
				
			||||||
            left: left,
 | 
					            left: left,
 | 
				
			||||||
            right: right
 | 
					            right: right
 | 
				
			||||||
            };
 | 
					          }
 | 
				
			||||||
          };
 | 
					        }
 | 
				
			||||||
        writeRange = function (range) {
 | 
					        writeRange = function (range) {
 | 
				
			||||||
            writeContent(word.toString());
 | 
					          writeContent(word.toString())
 | 
				
			||||||
            textfield.setSelectionRange(range.left, range.right);
 | 
					          textfield.setSelectionRange(range.left, range.right)
 | 
				
			||||||
          };
 | 
					        }
 | 
				
			||||||
          writeContent = function (content){
 | 
					        writeContent = function (content) {
 | 
				
			||||||
            textfield.value = content;
 | 
					          textfield.value = content
 | 
				
			||||||
          };
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        createRange = function (fix) {
 | 
					        createRange = function (fix) {
 | 
				
			||||||
            var range = {};
 | 
					          var range = {}
 | 
				
			||||||
            var s = domRoot.getSelection();
 | 
					          var s = domRoot.getSelection()
 | 
				
			||||||
            var clength = textfield.textContent.length;
 | 
					          var clength = textfield.textContent.length
 | 
				
			||||||
            range.left = Math.min(s.anchorOffset, clength);
 | 
					          range.left = Math.min(s.anchorOffset, clength)
 | 
				
			||||||
            range.right = Math.min(s.focusOffset, clength);
 | 
					          range.right = Math.min(s.focusOffset, clength)
 | 
				
			||||||
            if(fix != null){
 | 
					          if (fix != null) {
 | 
				
			||||||
              range.left = fix(range.left);
 | 
					            range.left = fix(range.left)
 | 
				
			||||||
              range.right = fix(range.right);
 | 
					            range.right = fix(range.right)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
            var editedElement = s.focusNode;
 | 
					          var editedElement = s.focusNode
 | 
				
			||||||
            if(editedElement === textfield || editedElement === textfield.childNodes[0]){
 | 
					          if (editedElement === textfield || editedElement === textfield.childNodes[0]) {
 | 
				
			||||||
              range.isReal = true;
 | 
					            range.isReal = true
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
              range.isReal = false;
 | 
					            range.isReal = false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return range
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            return range;
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        writeRange = function (range) {
 | 
					        writeRange = function (range) {
 | 
				
			||||||
            writeContent(word.toString());
 | 
					          writeContent(word.toString())
 | 
				
			||||||
            var textnode = textfield.childNodes[0];
 | 
					          var textnode = textfield.childNodes[0]
 | 
				
			||||||
            if(range.isReal && textnode != null) {
 | 
					          if (range.isReal && textnode != null) {
 | 
				
			||||||
              if(range.left < 0){
 | 
					            if (range.left < 0) {
 | 
				
			||||||
                range.left = 0;
 | 
					              range.left = 0
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
              range.right = Math.max(range.left, range.right);
 | 
					            range.right = Math.max(range.left, range.right)
 | 
				
			||||||
            if (range.right > textnode.length) {
 | 
					            if (range.right > textnode.length) {
 | 
				
			||||||
                range.right = textnode.length;
 | 
					              range.right = textnode.length
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            range.left = Math.min(range.left, range.right)
 | 
				
			||||||
 | 
					            var r = document.createRange(); // eslint-disable-line
 | 
				
			||||||
 | 
					            r.setStart(textnode, range.left)
 | 
				
			||||||
 | 
					            r.setEnd(textnode, range.right)
 | 
				
			||||||
 | 
					            var s = window.getSelection(); // eslint-disable-line
 | 
				
			||||||
 | 
					            s.removeAllRanges()
 | 
				
			||||||
 | 
					            s.addRange(r)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
              range.left = Math.min(range.left, range.right);
 | 
					 | 
				
			||||||
              var r = document.createRange(); //eslint-disable-line
 | 
					 | 
				
			||||||
              r.setStart(textnode, range.left);
 | 
					 | 
				
			||||||
              r.setEnd(textnode, range.right);
 | 
					 | 
				
			||||||
              var s = window.getSelection(); //eslint-disable-line
 | 
					 | 
				
			||||||
              s.removeAllRanges();
 | 
					 | 
				
			||||||
              s.addRange(r);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        writeContent = function (content) {
 | 
					        writeContent = function (content) {
 | 
				
			||||||
            var contentArray = content.replace(new RegExp("\n", 'g')," ").split(" ");//eslint-disable-line
 | 
					          var contentArray = content.replace(new RegExp('\n', 'g'), ' ').split(' ');// eslint-disable-line
 | 
				
			||||||
            textfield.innerText = "";
 | 
					          textfield.innerText = ''
 | 
				
			||||||
            for(var i in contentArray){
 | 
					          for (var i in contentArray) {
 | 
				
			||||||
              var c = contentArray[i];
 | 
					            var c = contentArray[i]
 | 
				
			||||||
              textfield.innerText += c;
 | 
					            textfield.innerText += c
 | 
				
			||||||
              if(i !== contentArray.length - 1){
 | 
					            if (i !== contentArray.length - 1) {
 | 
				
			||||||
                textfield.innerHTML += " ";
 | 
					              textfield.innerHTML += ' '
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        writeContent(this.toString());
 | 
					      }
 | 
				
			||||||
 | 
					      writeContent(this.toString())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.observe(function (events) {
 | 
					      this.observe(function (events) {
 | 
				
			||||||
          for(var e in events) {
 | 
					        for (var e in events) {
 | 
				
			||||||
            var event = events[e];
 | 
					          var event = events[e]
 | 
				
			||||||
          if (!creatorToken) {
 | 
					          if (!creatorToken) {
 | 
				
			||||||
              var oPos, fix;
 | 
					            var oPos, fix
 | 
				
			||||||
              if( event.type === "insert") {
 | 
					            if (event.type === 'insert') {
 | 
				
			||||||
                oPos = event.index;
 | 
					              oPos = event.index
 | 
				
			||||||
                fix = function (cursor) {//eslint-disable-line
 | 
					              fix = function (cursor) {// eslint-disable-line
 | 
				
			||||||
                if (cursor <= oPos) {
 | 
					                if (cursor <= oPos) {
 | 
				
			||||||
                    return cursor;
 | 
					                  return cursor
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    cursor += 1;
 | 
					                  cursor += 1
 | 
				
			||||||
                    return cursor;
 | 
					                  return cursor
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                };
 | 
					              }
 | 
				
			||||||
                var r = createRange(fix);
 | 
					              var r = createRange(fix)
 | 
				
			||||||
                writeRange(r);
 | 
					              writeRange(r)
 | 
				
			||||||
              } else if (event.type === "delete") {
 | 
					            } else if (event.type === 'delete') {
 | 
				
			||||||
                oPos = event.index;
 | 
					              oPos = event.index
 | 
				
			||||||
                fix = function (cursor){//eslint-disable-line
 | 
					              fix = function (cursor) {// eslint-disable-line
 | 
				
			||||||
                if (cursor < oPos) {
 | 
					                if (cursor < oPos) {
 | 
				
			||||||
                    return cursor;
 | 
					                  return cursor
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    cursor -= 1;
 | 
					                  cursor -= 1
 | 
				
			||||||
                    return cursor;
 | 
					                  return cursor
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                };
 | 
					              }
 | 
				
			||||||
                r = createRange(fix);
 | 
					              r = createRange(fix)
 | 
				
			||||||
                writeRange(r);
 | 
					              writeRange(r)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        });
 | 
					      })
 | 
				
			||||||
      // consume all text-insert changes.
 | 
					      // consume all text-insert changes.
 | 
				
			||||||
      textfield.onkeypress = function (event) {
 | 
					      textfield.onkeypress = function (event) {
 | 
				
			||||||
        if (word.is_deleted) {
 | 
					        if (word.is_deleted) {
 | 
				
			||||||
          // if word is deleted, do not do anything ever again
 | 
					          // if word is deleted, do not do anything ever again
 | 
				
			||||||
            textfield.onkeypress = null;
 | 
					          textfield.onkeypress = null
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
          creatorToken = true;
 | 
					        creatorToken = true
 | 
				
			||||||
          var char;
 | 
					        var char
 | 
				
			||||||
        if (event.keyCode === 13) {
 | 
					        if (event.keyCode === 13) {
 | 
				
			||||||
            char = "\n";
 | 
					          char = '\n'
 | 
				
			||||||
        } else if (event.key != null) {
 | 
					        } else if (event.key != null) {
 | 
				
			||||||
          if (event.charCode === 32) {
 | 
					          if (event.charCode === 32) {
 | 
				
			||||||
              char = " ";
 | 
					            char = ' '
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
              char = event.key;
 | 
					            char = event.key
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            char = window.String.fromCharCode(event.keyCode); //eslint-disable-line
 | 
					          char = window.String.fromCharCode(event.keyCode); // eslint-disable-line
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (char.length > 1) {
 | 
					        if (char.length > 1) {
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
        } else if (char.length > 0) {
 | 
					        } else if (char.length > 0) {
 | 
				
			||||||
            var r = createRange();
 | 
					          var r = createRange()
 | 
				
			||||||
            var pos = Math.min(r.left, r.right, word.length);
 | 
					          var pos = Math.min(r.left, r.right, word.length)
 | 
				
			||||||
            var diff = Math.abs(r.right - r.left);
 | 
					          var diff = Math.abs(r.right - r.left)
 | 
				
			||||||
            word.delete(pos, diff);
 | 
					          word.delete(pos, diff)
 | 
				
			||||||
            word.insert(pos, char);
 | 
					          word.insert(pos, char)
 | 
				
			||||||
            r.left = pos + char.length;
 | 
					          r.left = pos + char.length
 | 
				
			||||||
            r.right = r.left;
 | 
					          r.right = r.left
 | 
				
			||||||
            writeRange(r);
 | 
					          writeRange(r)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        event.preventDefault()
 | 
				
			||||||
 | 
					        creatorToken = false
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          event.preventDefault();
 | 
					 | 
				
			||||||
          creatorToken = false;
 | 
					 | 
				
			||||||
          return false;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      textfield.onpaste = function (event) {
 | 
					      textfield.onpaste = function (event) {
 | 
				
			||||||
        if (word.is_deleted) {
 | 
					        if (word.is_deleted) {
 | 
				
			||||||
          // if word is deleted, do not do anything ever again
 | 
					          // if word is deleted, do not do anything ever again
 | 
				
			||||||
            textfield.onpaste = null;
 | 
					          textfield.onpaste = null
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        event.preventDefault()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          event.preventDefault();
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      textfield.oncut = function (event) {
 | 
					      textfield.oncut = function (event) {
 | 
				
			||||||
        if (word.is_deleted) {
 | 
					        if (word.is_deleted) {
 | 
				
			||||||
          // if word is deleted, do not do anything ever again
 | 
					          // if word is deleted, do not do anything ever again
 | 
				
			||||||
            textfield.oncut = null;
 | 
					          textfield.oncut = null
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        event.preventDefault()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          event.preventDefault();
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      //
 | 
					      //
 | 
				
			||||||
      // consume deletes. Note that
 | 
					      // consume deletes. Note that
 | 
				
			||||||
      //   chrome: won't consume deletions on keypress event.
 | 
					      //   chrome: won't consume deletions on keypress event.
 | 
				
			||||||
@ -196,92 +197,92 @@
 | 
				
			|||||||
      //     Every browser supports keyCode. Let's stick with it for now..
 | 
					      //     Every browser supports keyCode. Let's stick with it for now..
 | 
				
			||||||
      //
 | 
					      //
 | 
				
			||||||
      textfield.onkeydown = function (event) {
 | 
					      textfield.onkeydown = function (event) {
 | 
				
			||||||
          creatorToken = true;
 | 
					        creatorToken = true
 | 
				
			||||||
        if (word.is_deleted) {
 | 
					        if (word.is_deleted) {
 | 
				
			||||||
          // if word is deleted, do not do anything ever again
 | 
					          // if word is deleted, do not do anything ever again
 | 
				
			||||||
            textfield.onkeydown = null;
 | 
					          textfield.onkeydown = null
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
          var r = createRange();
 | 
					        var r = createRange()
 | 
				
			||||||
          var pos = Math.min(r.left, r.right, word.toString().length);
 | 
					        var pos = Math.min(r.left, r.right, word.toString().length)
 | 
				
			||||||
          var diff = Math.abs(r.left - r.right);
 | 
					        var diff = Math.abs(r.left - r.right)
 | 
				
			||||||
        if (event.keyCode != null && event.keyCode === 8) { // Backspace
 | 
					        if (event.keyCode != null && event.keyCode === 8) { // Backspace
 | 
				
			||||||
          if (diff > 0) {
 | 
					          if (diff > 0) {
 | 
				
			||||||
              word.delete(pos, diff);
 | 
					            word.delete(pos, diff)
 | 
				
			||||||
              r.left = pos;
 | 
					            r.left = pos
 | 
				
			||||||
              r.right = pos;
 | 
					            r.right = pos
 | 
				
			||||||
              writeRange(r);
 | 
					            writeRange(r)
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            if (event.ctrlKey != null && event.ctrlKey) {
 | 
					            if (event.ctrlKey != null && event.ctrlKey) {
 | 
				
			||||||
                var val = word.toString();
 | 
					              var val = word.toString()
 | 
				
			||||||
                var newPos = pos;
 | 
					              var newPos = pos
 | 
				
			||||||
                var delLength = 0;
 | 
					              var delLength = 0
 | 
				
			||||||
              if (pos > 0) {
 | 
					              if (pos > 0) {
 | 
				
			||||||
                  newPos--;
 | 
					                newPos--
 | 
				
			||||||
                  delLength++;
 | 
					                delLength++
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
                while (newPos > 0 && val[newPos] !== " " && val[newPos] !== "\n") {
 | 
					              while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') {
 | 
				
			||||||
                  newPos--;
 | 
					                newPos--
 | 
				
			||||||
                  delLength++;
 | 
					                delLength++
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
                word.delete(newPos, pos - newPos);
 | 
					              word.delete(newPos, pos - newPos)
 | 
				
			||||||
                r.left = newPos;
 | 
					              r.left = newPos
 | 
				
			||||||
                r.right = newPos;
 | 
					              r.right = newPos
 | 
				
			||||||
                writeRange(r);
 | 
					              writeRange(r)
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              if (pos > 0) {
 | 
					              if (pos > 0) {
 | 
				
			||||||
                  word.delete(pos - 1, 1);
 | 
					                word.delete(pos - 1, 1)
 | 
				
			||||||
                  r.left = pos - 1;
 | 
					                r.left = pos - 1
 | 
				
			||||||
                  r.right = pos - 1;
 | 
					                r.right = pos - 1
 | 
				
			||||||
                  writeRange(r);
 | 
					                writeRange(r)
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
            event.preventDefault();
 | 
					          event.preventDefault()
 | 
				
			||||||
            creatorToken = false;
 | 
					          creatorToken = false
 | 
				
			||||||
            return false;
 | 
					          return false
 | 
				
			||||||
        } else if (event.keyCode != null && event.keyCode === 46) { // Delete
 | 
					        } else if (event.keyCode != null && event.keyCode === 46) { // Delete
 | 
				
			||||||
          if (diff > 0) {
 | 
					          if (diff > 0) {
 | 
				
			||||||
              word.delete(pos, diff);
 | 
					            word.delete(pos, diff)
 | 
				
			||||||
              r.left = pos;
 | 
					            r.left = pos
 | 
				
			||||||
              r.right = pos;
 | 
					            r.right = pos
 | 
				
			||||||
              writeRange(r);
 | 
					            writeRange(r)
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
              word.delete(pos, 1);
 | 
					            word.delete(pos, 1)
 | 
				
			||||||
              r.left = pos;
 | 
					            r.left = pos
 | 
				
			||||||
              r.right = pos;
 | 
					            r.right = pos
 | 
				
			||||||
              writeRange(r);
 | 
					            writeRange(r)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
            event.preventDefault();
 | 
					          event.preventDefault()
 | 
				
			||||||
            creatorToken = false;
 | 
					          creatorToken = false
 | 
				
			||||||
            return false;
 | 
					          return false
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            creatorToken = false;
 | 
					          creatorToken = false
 | 
				
			||||||
            return true;
 | 
					          return true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  Y.TextBind = new CustomType({
 | 
					  Y.TextBind = new CustomType({
 | 
				
			||||||
    class: YTextBind,
 | 
					    class: YTextBind,
 | 
				
			||||||
    createType: function* YTextBindCreator () {
 | 
					    createType: function * YTextBindCreator () {
 | 
				
			||||||
      var model = {
 | 
					      var model = {
 | 
				
			||||||
        start: null,
 | 
					        start: null,
 | 
				
			||||||
        end: null,
 | 
					        end: null,
 | 
				
			||||||
        struct: "List",
 | 
					        struct: 'List',
 | 
				
			||||||
        type: "TextBind",
 | 
					        type: 'TextBind',
 | 
				
			||||||
        id: this.store.getNextOpId()
 | 
					        id: this.store.getNextOpId()
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      yield* this.applyCreatedOperations([model]);
 | 
					 | 
				
			||||||
      return yield* this.createType(model);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    initType: function* YTextBindInitializer(os, model){
 | 
					 | 
				
			||||||
      var valArray = [];
 | 
					 | 
				
			||||||
      var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
 | 
					 | 
				
			||||||
        valArray.push(c.content);
 | 
					 | 
				
			||||||
        return JSON.stringify(c.id);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return new YTextBind(os, model.id, idArray, valArray);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
  });
 | 
					      yield* this.applyCreatedOperations([model])
 | 
				
			||||||
})();
 | 
					      return yield* this.createType(model)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    initType: function * YTextBindInitializer (os, model) {
 | 
				
			||||||
 | 
					      var valArray = []
 | 
				
			||||||
 | 
					      var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
 | 
				
			||||||
 | 
					        valArray.push(c.content)
 | 
				
			||||||
 | 
					        return JSON.stringify(c.id)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      return new YTextBind(os, model.id, idArray, valArray)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										85
									
								
								src/Utils.js
									
									
									
									
									
								
							
							
						
						
									
										85
									
								
								src/Utils.js
									
									
									
									
									
								
							@ -1,100 +1,101 @@
 | 
				
			|||||||
 | 
					/* global copyObject, compareIds */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var GeneratorFunction = (function*(){}).constructor;//eslint-disable-line
 | 
					var GeneratorFunction = (function *() {}).constructor;// eslint-disable-line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EventHandler { //eslint-disable-line
 | 
					class EventHandler { // eslint-disable-line
 | 
				
			||||||
  constructor (onevent) {
 | 
					  constructor (onevent) {
 | 
				
			||||||
    this.waiting = [];
 | 
					    this.waiting = []
 | 
				
			||||||
    this.awaiting = 0;
 | 
					    this.awaiting = 0
 | 
				
			||||||
    this.onevent = onevent;
 | 
					    this.onevent = onevent
 | 
				
			||||||
    this.userEventListeners = [];
 | 
					    this.userEventListeners = []
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  receivedOp (op) {
 | 
					  receivedOp (op) {
 | 
				
			||||||
    if (this.awaiting <= 0) {
 | 
					    if (this.awaiting <= 0) {
 | 
				
			||||||
      this.onevent([op]);
 | 
					      this.onevent([op])
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.waiting.push(copyObject(op));
 | 
					      this.waiting.push(copyObject(op))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  awaitAndPrematurelyCall (ops) {
 | 
					  awaitAndPrematurelyCall (ops) {
 | 
				
			||||||
    this.awaiting++;
 | 
					    this.awaiting++
 | 
				
			||||||
    this.onevent(ops);
 | 
					    this.onevent(ops)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  addUserEventListener (f) {
 | 
					  addUserEventListener (f) {
 | 
				
			||||||
    this.userEventListeners.push(f);
 | 
					    this.userEventListeners.push(f)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  removeUserEventListener (f) {
 | 
					  removeUserEventListener (f) {
 | 
				
			||||||
    this.userEventListeners = this.userEventListeners.filter(function(g){
 | 
					    this.userEventListeners = this.userEventListeners.filter(function (g) {
 | 
				
			||||||
      return f !== g;
 | 
					      return f !== g
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  removeAllUserEventListeners () {
 | 
					  removeAllUserEventListeners () {
 | 
				
			||||||
    this.userEventListeners = [];
 | 
					    this.userEventListeners = []
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  callUserEventListeners (event) {
 | 
					  callUserEventListeners (event) {
 | 
				
			||||||
    for (var i in this.userEventListeners) {
 | 
					    for (var i in this.userEventListeners) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        this.userEventListeners[i](event);
 | 
					        this.userEventListeners[i](event)
 | 
				
			||||||
      } catch (e) {
 | 
					      } catch (e) {
 | 
				
			||||||
        console.log("User events must not throw Errors!");//eslint-disable-line
 | 
					        console.log('User events must not throw Errors!');// eslint-disable-line
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  awaitedLastInserts (n) {
 | 
					  awaitedLastInserts (n) {
 | 
				
			||||||
    var ops = this.waiting.splice(this.waiting.length - n);
 | 
					    var ops = this.waiting.splice(this.waiting.length - n)
 | 
				
			||||||
    for (var oid = 0; oid < ops.length; oid++) {
 | 
					    for (var oid = 0; oid < ops.length; oid++) {
 | 
				
			||||||
      var op = ops[oid];
 | 
					      var op = ops[oid]
 | 
				
			||||||
      for (var i = this.waiting.length - 1; i >= 0; i--) {
 | 
					      for (var i = this.waiting.length - 1; i >= 0; i--) {
 | 
				
			||||||
        let w = this.waiting[i];
 | 
					        let w = this.waiting[i]
 | 
				
			||||||
        if (compareIds(op.left, w.id)) {
 | 
					        if (compareIds(op.left, w.id)) {
 | 
				
			||||||
          // include the effect of op in w
 | 
					          // include the effect of op in w
 | 
				
			||||||
          w.right = op.id;
 | 
					          w.right = op.id
 | 
				
			||||||
          // exclude the effect of w in op
 | 
					          // exclude the effect of w in op
 | 
				
			||||||
          op.left = w.left;
 | 
					          op.left = w.left
 | 
				
			||||||
        } else if (compareIds(op.right, w.id)) {
 | 
					        } else if (compareIds(op.right, w.id)) {
 | 
				
			||||||
          // similar..
 | 
					          // similar..
 | 
				
			||||||
          w.left = op.id;
 | 
					          w.left = op.id
 | 
				
			||||||
          op.right = w.right;
 | 
					          op.right = w.right
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.tryCallEvents();
 | 
					    this.tryCallEvents()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  awaitedLastDeletes (n, newLeft) {
 | 
					  awaitedLastDeletes (n, newLeft) {
 | 
				
			||||||
    var ops = this.waiting.splice(this.waiting.length - n);
 | 
					    var ops = this.waiting.splice(this.waiting.length - n)
 | 
				
			||||||
    for (var j in ops) {
 | 
					    for (var j in ops) {
 | 
				
			||||||
      var del = ops[j];
 | 
					      var del = ops[j]
 | 
				
			||||||
      if (newLeft != null) {
 | 
					      if (newLeft != null) {
 | 
				
			||||||
        for (var i in this.waiting) {
 | 
					        for (var i in this.waiting) {
 | 
				
			||||||
          let w = this.waiting[i];
 | 
					          let w = this.waiting[i]
 | 
				
			||||||
          // We will just care about w.left
 | 
					          // We will just care about w.left
 | 
				
			||||||
          if (compareIds(del.target, w.left)) {
 | 
					          if (compareIds(del.target, w.left)) {
 | 
				
			||||||
            del.left = newLeft;
 | 
					            del.left = newLeft
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.tryCallEvents();
 | 
					    this.tryCallEvents()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  tryCallEvents () {
 | 
					  tryCallEvents () {
 | 
				
			||||||
    this.awaiting--;
 | 
					    this.awaiting--
 | 
				
			||||||
    if (this.awaiting <= 0 && this.waiting.length > 0) {
 | 
					    if (this.awaiting <= 0 && this.waiting.length > 0) {
 | 
				
			||||||
      var events = this.waiting;
 | 
					      var events = this.waiting
 | 
				
			||||||
      this.waiting = [];
 | 
					      this.waiting = []
 | 
				
			||||||
      this.onevent(events);
 | 
					      this.onevent(events)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomType { //eslint-disable-line
 | 
					class CustomType { // eslint-disable-line
 | 
				
			||||||
  constructor (def) {
 | 
					  constructor (def) {
 | 
				
			||||||
    if (def.createType == null
 | 
					    if (def.createType == null ||
 | 
				
			||||||
      || def.initType == null
 | 
					      def.initType == null ||
 | 
				
			||||||
      || def.class == null
 | 
					      def.class == null
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      throw new Error("Custom type was not initialized correctly!");
 | 
					      throw new Error('Custom type was not initialized correctly!')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.createType = def.createType;
 | 
					    this.createType = def.createType
 | 
				
			||||||
    this.initType = def.initType;
 | 
					    this.initType = def.initType
 | 
				
			||||||
    this.class = def.class;
 | 
					    this.class = def.class
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user